18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci//
38c2ecf20Sopenharmony_ci// Ingenic JZ47xx IPU driver
48c2ecf20Sopenharmony_ci//
58c2ecf20Sopenharmony_ci// Copyright (C) 2020, Paul Cercueil <paul@crapouillou.net>
68c2ecf20Sopenharmony_ci// Copyright (C) 2020, Daniel Silsby <dansilsby@gmail.com>
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci#include "ingenic-drm.h"
98c2ecf20Sopenharmony_ci#include "ingenic-ipu.h"
108c2ecf20Sopenharmony_ci
118c2ecf20Sopenharmony_ci#include <linux/clk.h>
128c2ecf20Sopenharmony_ci#include <linux/component.h>
138c2ecf20Sopenharmony_ci#include <linux/gcd.h>
148c2ecf20Sopenharmony_ci#include <linux/interrupt.h>
158c2ecf20Sopenharmony_ci#include <linux/module.h>
168c2ecf20Sopenharmony_ci#include <linux/of.h>
178c2ecf20Sopenharmony_ci#include <linux/of_device.h>
188c2ecf20Sopenharmony_ci#include <linux/regmap.h>
198c2ecf20Sopenharmony_ci#include <linux/time.h>
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_ci#include <drm/drm_atomic.h>
228c2ecf20Sopenharmony_ci#include <drm/drm_atomic_helper.h>
238c2ecf20Sopenharmony_ci#include <drm/drm_drv.h>
248c2ecf20Sopenharmony_ci#include <drm/drm_fb_cma_helper.h>
258c2ecf20Sopenharmony_ci#include <drm/drm_fourcc.h>
268c2ecf20Sopenharmony_ci#include <drm/drm_gem_framebuffer_helper.h>
278c2ecf20Sopenharmony_ci#include <drm/drm_plane.h>
288c2ecf20Sopenharmony_ci#include <drm/drm_plane_helper.h>
298c2ecf20Sopenharmony_ci#include <drm/drm_property.h>
308c2ecf20Sopenharmony_ci#include <drm/drm_vblank.h>
318c2ecf20Sopenharmony_ci
328c2ecf20Sopenharmony_cistruct ingenic_ipu;
338c2ecf20Sopenharmony_ci
348c2ecf20Sopenharmony_cistruct soc_info {
358c2ecf20Sopenharmony_ci	const u32 *formats;
368c2ecf20Sopenharmony_ci	size_t num_formats;
378c2ecf20Sopenharmony_ci	bool has_bicubic;
388c2ecf20Sopenharmony_ci	bool manual_restart;
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_ci	void (*set_coefs)(struct ingenic_ipu *ipu, unsigned int reg,
418c2ecf20Sopenharmony_ci			  unsigned int sharpness, bool downscale,
428c2ecf20Sopenharmony_ci			  unsigned int weight, unsigned int offset);
438c2ecf20Sopenharmony_ci};
448c2ecf20Sopenharmony_ci
458c2ecf20Sopenharmony_cistruct ingenic_ipu {
468c2ecf20Sopenharmony_ci	struct drm_plane plane;
478c2ecf20Sopenharmony_ci	struct drm_device *drm;
488c2ecf20Sopenharmony_ci	struct device *dev, *master;
498c2ecf20Sopenharmony_ci	struct regmap *map;
508c2ecf20Sopenharmony_ci	struct clk *clk;
518c2ecf20Sopenharmony_ci	const struct soc_info *soc_info;
528c2ecf20Sopenharmony_ci	bool clk_enabled;
538c2ecf20Sopenharmony_ci
548c2ecf20Sopenharmony_ci	unsigned int num_w, num_h, denom_w, denom_h;
558c2ecf20Sopenharmony_ci
568c2ecf20Sopenharmony_ci	dma_addr_t addr_y, addr_u, addr_v;
578c2ecf20Sopenharmony_ci
588c2ecf20Sopenharmony_ci	struct drm_property *sharpness_prop;
598c2ecf20Sopenharmony_ci	unsigned int sharpness;
608c2ecf20Sopenharmony_ci};
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_ci/* Signed 15.16 fixed-point math (for bicubic scaling coefficients) */
638c2ecf20Sopenharmony_ci#define I2F(i) ((s32)(i) * 65536)
648c2ecf20Sopenharmony_ci#define F2I(f) ((f) / 65536)
658c2ecf20Sopenharmony_ci#define FMUL(fa, fb) ((s32)(((s64)(fa) * (s64)(fb)) / 65536))
668c2ecf20Sopenharmony_ci#define SHARPNESS_INCR (I2F(-1) / 8)
678c2ecf20Sopenharmony_ci
688c2ecf20Sopenharmony_cistatic inline struct ingenic_ipu *plane_to_ingenic_ipu(struct drm_plane *plane)
698c2ecf20Sopenharmony_ci{
708c2ecf20Sopenharmony_ci	return container_of(plane, struct ingenic_ipu, plane);
718c2ecf20Sopenharmony_ci}
728c2ecf20Sopenharmony_ci
738c2ecf20Sopenharmony_ci/*
748c2ecf20Sopenharmony_ci * Apply conventional cubic convolution kernel. Both parameters
758c2ecf20Sopenharmony_ci *  and return value are 15.16 signed fixed-point.
768c2ecf20Sopenharmony_ci *
778c2ecf20Sopenharmony_ci *  @f_a: Sharpness factor, typically in range [-4.0, -0.25].
788c2ecf20Sopenharmony_ci *        A larger magnitude increases perceived sharpness, but going past
798c2ecf20Sopenharmony_ci *        -2.0 might cause ringing artifacts to outweigh any improvement.
808c2ecf20Sopenharmony_ci *        Nice values on a 320x240 LCD are between -0.75 and -2.0.
818c2ecf20Sopenharmony_ci *
828c2ecf20Sopenharmony_ci *  @f_x: Absolute distance in pixels from 'pixel 0' sample position
838c2ecf20Sopenharmony_ci *        along horizontal (or vertical) source axis. Range is [0, +2.0].
848c2ecf20Sopenharmony_ci *
858c2ecf20Sopenharmony_ci *  returns: Weight of this pixel within 4-pixel sample group. Range is
868c2ecf20Sopenharmony_ci *           [-2.0, +2.0]. For moderate (i.e. > -3.0) sharpness factors,
878c2ecf20Sopenharmony_ci *           range is within [-1.0, +1.0].
888c2ecf20Sopenharmony_ci */
898c2ecf20Sopenharmony_cistatic inline s32 cubic_conv(s32 f_a, s32 f_x)
908c2ecf20Sopenharmony_ci{
918c2ecf20Sopenharmony_ci	const s32 f_1 = I2F(1);
928c2ecf20Sopenharmony_ci	const s32 f_2 = I2F(2);
938c2ecf20Sopenharmony_ci	const s32 f_3 = I2F(3);
948c2ecf20Sopenharmony_ci	const s32 f_4 = I2F(4);
958c2ecf20Sopenharmony_ci	const s32 f_x2 = FMUL(f_x, f_x);
968c2ecf20Sopenharmony_ci	const s32 f_x3 = FMUL(f_x, f_x2);
978c2ecf20Sopenharmony_ci
988c2ecf20Sopenharmony_ci	if (f_x <= f_1)
998c2ecf20Sopenharmony_ci		return FMUL((f_a + f_2), f_x3) - FMUL((f_a + f_3), f_x2) + f_1;
1008c2ecf20Sopenharmony_ci	else if (f_x <= f_2)
1018c2ecf20Sopenharmony_ci		return FMUL(f_a, (f_x3 - 5 * f_x2 + 8 * f_x - f_4));
1028c2ecf20Sopenharmony_ci	else
1038c2ecf20Sopenharmony_ci		return 0;
1048c2ecf20Sopenharmony_ci}
1058c2ecf20Sopenharmony_ci
1068c2ecf20Sopenharmony_ci/*
1078c2ecf20Sopenharmony_ci * On entry, "weight" is a coefficient suitable for bilinear mode,
1088c2ecf20Sopenharmony_ci *  which is converted to a set of four suitable for bicubic mode.
1098c2ecf20Sopenharmony_ci *
1108c2ecf20Sopenharmony_ci * "weight 512" means all of pixel 0;
1118c2ecf20Sopenharmony_ci * "weight 256" means half of pixel 0 and half of pixel 1;
1128c2ecf20Sopenharmony_ci * "weight 0" means all of pixel 1;
1138c2ecf20Sopenharmony_ci *
1148c2ecf20Sopenharmony_ci * "offset" is increment to next source pixel sample location.
1158c2ecf20Sopenharmony_ci */
1168c2ecf20Sopenharmony_cistatic void jz4760_set_coefs(struct ingenic_ipu *ipu, unsigned int reg,
1178c2ecf20Sopenharmony_ci			     unsigned int sharpness, bool downscale,
1188c2ecf20Sopenharmony_ci			     unsigned int weight, unsigned int offset)
1198c2ecf20Sopenharmony_ci{
1208c2ecf20Sopenharmony_ci	u32 val;
1218c2ecf20Sopenharmony_ci	s32 w0, w1, w2, w3; /* Pixel weights at X (or Y) offsets -1,0,1,2 */
1228c2ecf20Sopenharmony_ci
1238c2ecf20Sopenharmony_ci	weight = clamp_val(weight, 0, 512);
1248c2ecf20Sopenharmony_ci
1258c2ecf20Sopenharmony_ci	if (sharpness < 2) {
1268c2ecf20Sopenharmony_ci		/*
1278c2ecf20Sopenharmony_ci		 *  When sharpness setting is 0, emulate nearest-neighbor.
1288c2ecf20Sopenharmony_ci		 *  When sharpness setting is 1, emulate bilinear.
1298c2ecf20Sopenharmony_ci		 */
1308c2ecf20Sopenharmony_ci
1318c2ecf20Sopenharmony_ci		if (sharpness == 0)
1328c2ecf20Sopenharmony_ci			weight = weight >= 256 ? 512 : 0;
1338c2ecf20Sopenharmony_ci		w0 = 0;
1348c2ecf20Sopenharmony_ci		w1 = weight;
1358c2ecf20Sopenharmony_ci		w2 = 512 - weight;
1368c2ecf20Sopenharmony_ci		w3 = 0;
1378c2ecf20Sopenharmony_ci	} else {
1388c2ecf20Sopenharmony_ci		const s32 f_a = SHARPNESS_INCR * sharpness;
1398c2ecf20Sopenharmony_ci		const s32 f_h = I2F(1) / 2; /* Round up 0.5 */
1408c2ecf20Sopenharmony_ci
1418c2ecf20Sopenharmony_ci		/*
1428c2ecf20Sopenharmony_ci		 * Note that always rounding towards +infinity here is intended.
1438c2ecf20Sopenharmony_ci		 * The resulting coefficients match a round-to-nearest-int
1448c2ecf20Sopenharmony_ci		 * double floating-point implementation.
1458c2ecf20Sopenharmony_ci		 */
1468c2ecf20Sopenharmony_ci
1478c2ecf20Sopenharmony_ci		weight = 512 - weight;
1488c2ecf20Sopenharmony_ci		w0 = F2I(f_h + 512 * cubic_conv(f_a, I2F(512  + weight) / 512));
1498c2ecf20Sopenharmony_ci		w1 = F2I(f_h + 512 * cubic_conv(f_a, I2F(0    + weight) / 512));
1508c2ecf20Sopenharmony_ci		w2 = F2I(f_h + 512 * cubic_conv(f_a, I2F(512  - weight) / 512));
1518c2ecf20Sopenharmony_ci		w3 = F2I(f_h + 512 * cubic_conv(f_a, I2F(1024 - weight) / 512));
1528c2ecf20Sopenharmony_ci		w0 = clamp_val(w0, -1024, 1023);
1538c2ecf20Sopenharmony_ci		w1 = clamp_val(w1, -1024, 1023);
1548c2ecf20Sopenharmony_ci		w2 = clamp_val(w2, -1024, 1023);
1558c2ecf20Sopenharmony_ci		w3 = clamp_val(w3, -1024, 1023);
1568c2ecf20Sopenharmony_ci	}
1578c2ecf20Sopenharmony_ci
1588c2ecf20Sopenharmony_ci	val = ((w1 & JZ4760_IPU_RSZ_COEF_MASK) << JZ4760_IPU_RSZ_COEF31_LSB) |
1598c2ecf20Sopenharmony_ci		((w0 & JZ4760_IPU_RSZ_COEF_MASK) << JZ4760_IPU_RSZ_COEF20_LSB);
1608c2ecf20Sopenharmony_ci	regmap_write(ipu->map, reg, val);
1618c2ecf20Sopenharmony_ci
1628c2ecf20Sopenharmony_ci	val = ((w3 & JZ4760_IPU_RSZ_COEF_MASK) << JZ4760_IPU_RSZ_COEF31_LSB) |
1638c2ecf20Sopenharmony_ci		((w2 & JZ4760_IPU_RSZ_COEF_MASK) << JZ4760_IPU_RSZ_COEF20_LSB) |
1648c2ecf20Sopenharmony_ci		((offset & JZ4760_IPU_RSZ_OFFSET_MASK) << JZ4760_IPU_RSZ_OFFSET_LSB);
1658c2ecf20Sopenharmony_ci	regmap_write(ipu->map, reg, val);
1668c2ecf20Sopenharmony_ci}
1678c2ecf20Sopenharmony_ci
1688c2ecf20Sopenharmony_cistatic void jz4725b_set_coefs(struct ingenic_ipu *ipu, unsigned int reg,
1698c2ecf20Sopenharmony_ci			      unsigned int sharpness, bool downscale,
1708c2ecf20Sopenharmony_ci			      unsigned int weight, unsigned int offset)
1718c2ecf20Sopenharmony_ci{
1728c2ecf20Sopenharmony_ci	u32 val = JZ4725B_IPU_RSZ_LUT_OUT_EN;
1738c2ecf20Sopenharmony_ci	unsigned int i;
1748c2ecf20Sopenharmony_ci
1758c2ecf20Sopenharmony_ci	weight = clamp_val(weight, 0, 512);
1768c2ecf20Sopenharmony_ci
1778c2ecf20Sopenharmony_ci	if (sharpness == 0)
1788c2ecf20Sopenharmony_ci		weight = weight >= 256 ? 512 : 0;
1798c2ecf20Sopenharmony_ci
1808c2ecf20Sopenharmony_ci	val |= (weight & JZ4725B_IPU_RSZ_LUT_COEF_MASK) << JZ4725B_IPU_RSZ_LUT_COEF_LSB;
1818c2ecf20Sopenharmony_ci	if (downscale || !!offset)
1828c2ecf20Sopenharmony_ci		val |= JZ4725B_IPU_RSZ_LUT_IN_EN;
1838c2ecf20Sopenharmony_ci
1848c2ecf20Sopenharmony_ci	regmap_write(ipu->map, reg, val);
1858c2ecf20Sopenharmony_ci
1868c2ecf20Sopenharmony_ci	if (downscale) {
1878c2ecf20Sopenharmony_ci		for (i = 1; i < offset; i++)
1888c2ecf20Sopenharmony_ci			regmap_write(ipu->map, reg, JZ4725B_IPU_RSZ_LUT_IN_EN);
1898c2ecf20Sopenharmony_ci	}
1908c2ecf20Sopenharmony_ci}
1918c2ecf20Sopenharmony_ci
1928c2ecf20Sopenharmony_cistatic void ingenic_ipu_set_downscale_coefs(struct ingenic_ipu *ipu,
1938c2ecf20Sopenharmony_ci					    unsigned int reg,
1948c2ecf20Sopenharmony_ci					    unsigned int num,
1958c2ecf20Sopenharmony_ci					    unsigned int denom)
1968c2ecf20Sopenharmony_ci{
1978c2ecf20Sopenharmony_ci	unsigned int i, offset, weight, weight_num = denom;
1988c2ecf20Sopenharmony_ci
1998c2ecf20Sopenharmony_ci	for (i = 0; i < num; i++) {
2008c2ecf20Sopenharmony_ci		weight_num = num + (weight_num - num) % (num * 2);
2018c2ecf20Sopenharmony_ci		weight = 512 - 512 * (weight_num - num) / (num * 2);
2028c2ecf20Sopenharmony_ci		weight_num += denom * 2;
2038c2ecf20Sopenharmony_ci		offset = (weight_num - num) / (num * 2);
2048c2ecf20Sopenharmony_ci
2058c2ecf20Sopenharmony_ci		ipu->soc_info->set_coefs(ipu, reg, ipu->sharpness,
2068c2ecf20Sopenharmony_ci					 true, weight, offset);
2078c2ecf20Sopenharmony_ci	}
2088c2ecf20Sopenharmony_ci}
2098c2ecf20Sopenharmony_ci
2108c2ecf20Sopenharmony_cistatic void ingenic_ipu_set_integer_upscale_coefs(struct ingenic_ipu *ipu,
2118c2ecf20Sopenharmony_ci						  unsigned int reg,
2128c2ecf20Sopenharmony_ci						  unsigned int num)
2138c2ecf20Sopenharmony_ci{
2148c2ecf20Sopenharmony_ci	/*
2158c2ecf20Sopenharmony_ci	 * Force nearest-neighbor scaling and use simple math when upscaling
2168c2ecf20Sopenharmony_ci	 * by an integer ratio. It looks better, and fixes a few problem cases.
2178c2ecf20Sopenharmony_ci	 */
2188c2ecf20Sopenharmony_ci	unsigned int i;
2198c2ecf20Sopenharmony_ci
2208c2ecf20Sopenharmony_ci	for (i = 0; i < num; i++)
2218c2ecf20Sopenharmony_ci		ipu->soc_info->set_coefs(ipu, reg, 0, false, 512, i == num - 1);
2228c2ecf20Sopenharmony_ci}
2238c2ecf20Sopenharmony_ci
2248c2ecf20Sopenharmony_cistatic void ingenic_ipu_set_upscale_coefs(struct ingenic_ipu *ipu,
2258c2ecf20Sopenharmony_ci					  unsigned int reg,
2268c2ecf20Sopenharmony_ci					  unsigned int num,
2278c2ecf20Sopenharmony_ci					  unsigned int denom)
2288c2ecf20Sopenharmony_ci{
2298c2ecf20Sopenharmony_ci	unsigned int i, offset, weight, weight_num = 0;
2308c2ecf20Sopenharmony_ci
2318c2ecf20Sopenharmony_ci	for (i = 0; i < num; i++) {
2328c2ecf20Sopenharmony_ci		weight = 512 - 512 * weight_num / num;
2338c2ecf20Sopenharmony_ci		weight_num += denom;
2348c2ecf20Sopenharmony_ci		offset = weight_num >= num;
2358c2ecf20Sopenharmony_ci
2368c2ecf20Sopenharmony_ci		if (offset)
2378c2ecf20Sopenharmony_ci			weight_num -= num;
2388c2ecf20Sopenharmony_ci
2398c2ecf20Sopenharmony_ci		ipu->soc_info->set_coefs(ipu, reg, ipu->sharpness,
2408c2ecf20Sopenharmony_ci					 false, weight, offset);
2418c2ecf20Sopenharmony_ci	}
2428c2ecf20Sopenharmony_ci}
2438c2ecf20Sopenharmony_ci
2448c2ecf20Sopenharmony_cistatic void ingenic_ipu_set_coefs(struct ingenic_ipu *ipu, unsigned int reg,
2458c2ecf20Sopenharmony_ci				  unsigned int num, unsigned int denom)
2468c2ecf20Sopenharmony_ci{
2478c2ecf20Sopenharmony_ci	/* Begin programming the LUT */
2488c2ecf20Sopenharmony_ci	regmap_write(ipu->map, reg, -1);
2498c2ecf20Sopenharmony_ci
2508c2ecf20Sopenharmony_ci	if (denom > num)
2518c2ecf20Sopenharmony_ci		ingenic_ipu_set_downscale_coefs(ipu, reg, num, denom);
2528c2ecf20Sopenharmony_ci	else if (denom == 1)
2538c2ecf20Sopenharmony_ci		ingenic_ipu_set_integer_upscale_coefs(ipu, reg, num);
2548c2ecf20Sopenharmony_ci	else
2558c2ecf20Sopenharmony_ci		ingenic_ipu_set_upscale_coefs(ipu, reg, num, denom);
2568c2ecf20Sopenharmony_ci}
2578c2ecf20Sopenharmony_ci
2588c2ecf20Sopenharmony_cistatic int reduce_fraction(unsigned int *num, unsigned int *denom)
2598c2ecf20Sopenharmony_ci{
2608c2ecf20Sopenharmony_ci	unsigned long d = gcd(*num, *denom);
2618c2ecf20Sopenharmony_ci
2628c2ecf20Sopenharmony_ci	/* The scaling table has only 31 entries */
2638c2ecf20Sopenharmony_ci	if (*num > 31 * d)
2648c2ecf20Sopenharmony_ci		return -EINVAL;
2658c2ecf20Sopenharmony_ci
2668c2ecf20Sopenharmony_ci	*num /= d;
2678c2ecf20Sopenharmony_ci	*denom /= d;
2688c2ecf20Sopenharmony_ci	return 0;
2698c2ecf20Sopenharmony_ci}
2708c2ecf20Sopenharmony_ci
2718c2ecf20Sopenharmony_cistatic inline bool osd_changed(struct drm_plane_state *state,
2728c2ecf20Sopenharmony_ci			       struct drm_plane_state *oldstate)
2738c2ecf20Sopenharmony_ci{
2748c2ecf20Sopenharmony_ci	return state->src_x != oldstate->src_x ||
2758c2ecf20Sopenharmony_ci		state->src_y != oldstate->src_y ||
2768c2ecf20Sopenharmony_ci		state->src_w != oldstate->src_w ||
2778c2ecf20Sopenharmony_ci		state->src_h != oldstate->src_h ||
2788c2ecf20Sopenharmony_ci		state->crtc_x != oldstate->crtc_x ||
2798c2ecf20Sopenharmony_ci		state->crtc_y != oldstate->crtc_y ||
2808c2ecf20Sopenharmony_ci		state->crtc_w != oldstate->crtc_w ||
2818c2ecf20Sopenharmony_ci		state->crtc_h != oldstate->crtc_h;
2828c2ecf20Sopenharmony_ci}
2838c2ecf20Sopenharmony_ci
2848c2ecf20Sopenharmony_cistatic void ingenic_ipu_plane_atomic_update(struct drm_plane *plane,
2858c2ecf20Sopenharmony_ci					    struct drm_plane_state *oldstate)
2868c2ecf20Sopenharmony_ci{
2878c2ecf20Sopenharmony_ci	struct ingenic_ipu *ipu = plane_to_ingenic_ipu(plane);
2888c2ecf20Sopenharmony_ci	struct drm_plane_state *state = plane->state;
2898c2ecf20Sopenharmony_ci	const struct drm_format_info *finfo;
2908c2ecf20Sopenharmony_ci	u32 ctrl, stride = 0, coef_index = 0, format = 0;
2918c2ecf20Sopenharmony_ci	bool needs_modeset, upscaling_w, upscaling_h;
2928c2ecf20Sopenharmony_ci	int err;
2938c2ecf20Sopenharmony_ci
2948c2ecf20Sopenharmony_ci	if (!state || !state->fb)
2958c2ecf20Sopenharmony_ci		return;
2968c2ecf20Sopenharmony_ci
2978c2ecf20Sopenharmony_ci	finfo = drm_format_info(state->fb->format->format);
2988c2ecf20Sopenharmony_ci
2998c2ecf20Sopenharmony_ci	if (!ipu->clk_enabled) {
3008c2ecf20Sopenharmony_ci		err = clk_enable(ipu->clk);
3018c2ecf20Sopenharmony_ci		if (err) {
3028c2ecf20Sopenharmony_ci			dev_err(ipu->dev, "Unable to enable clock: %d\n", err);
3038c2ecf20Sopenharmony_ci			return;
3048c2ecf20Sopenharmony_ci		}
3058c2ecf20Sopenharmony_ci
3068c2ecf20Sopenharmony_ci		ipu->clk_enabled = true;
3078c2ecf20Sopenharmony_ci	}
3088c2ecf20Sopenharmony_ci
3098c2ecf20Sopenharmony_ci	/* Reset all the registers if needed */
3108c2ecf20Sopenharmony_ci	needs_modeset = drm_atomic_crtc_needs_modeset(state->crtc->state);
3118c2ecf20Sopenharmony_ci	if (needs_modeset) {
3128c2ecf20Sopenharmony_ci		regmap_set_bits(ipu->map, JZ_REG_IPU_CTRL, JZ_IPU_CTRL_RST);
3138c2ecf20Sopenharmony_ci
3148c2ecf20Sopenharmony_ci		/* Enable the chip */
3158c2ecf20Sopenharmony_ci		regmap_set_bits(ipu->map, JZ_REG_IPU_CTRL,
3168c2ecf20Sopenharmony_ci				JZ_IPU_CTRL_CHIP_EN | JZ_IPU_CTRL_LCDC_SEL);
3178c2ecf20Sopenharmony_ci	}
3188c2ecf20Sopenharmony_ci
3198c2ecf20Sopenharmony_ci	/* New addresses will be committed in vblank handler... */
3208c2ecf20Sopenharmony_ci	ipu->addr_y = drm_fb_cma_get_gem_addr(state->fb, state, 0);
3218c2ecf20Sopenharmony_ci	if (finfo->num_planes > 1)
3228c2ecf20Sopenharmony_ci		ipu->addr_u = drm_fb_cma_get_gem_addr(state->fb, state, 1);
3238c2ecf20Sopenharmony_ci	if (finfo->num_planes > 2)
3248c2ecf20Sopenharmony_ci		ipu->addr_v = drm_fb_cma_get_gem_addr(state->fb, state, 2);
3258c2ecf20Sopenharmony_ci
3268c2ecf20Sopenharmony_ci	if (!needs_modeset)
3278c2ecf20Sopenharmony_ci		return;
3288c2ecf20Sopenharmony_ci
3298c2ecf20Sopenharmony_ci	/* Or right here if we're doing a full modeset. */
3308c2ecf20Sopenharmony_ci	regmap_write(ipu->map, JZ_REG_IPU_Y_ADDR, ipu->addr_y);
3318c2ecf20Sopenharmony_ci	regmap_write(ipu->map, JZ_REG_IPU_U_ADDR, ipu->addr_u);
3328c2ecf20Sopenharmony_ci	regmap_write(ipu->map, JZ_REG_IPU_V_ADDR, ipu->addr_v);
3338c2ecf20Sopenharmony_ci
3348c2ecf20Sopenharmony_ci	if (finfo->num_planes == 1)
3358c2ecf20Sopenharmony_ci		regmap_set_bits(ipu->map, JZ_REG_IPU_CTRL, JZ_IPU_CTRL_SPKG_SEL);
3368c2ecf20Sopenharmony_ci
3378c2ecf20Sopenharmony_ci	ingenic_drm_plane_config(ipu->master, plane, DRM_FORMAT_XRGB8888);
3388c2ecf20Sopenharmony_ci
3398c2ecf20Sopenharmony_ci	/* Set the input height/width/strides */
3408c2ecf20Sopenharmony_ci	if (finfo->num_planes > 2)
3418c2ecf20Sopenharmony_ci		stride = ((state->src_w >> 16) * finfo->cpp[2] / finfo->hsub)
3428c2ecf20Sopenharmony_ci			<< JZ_IPU_UV_STRIDE_V_LSB;
3438c2ecf20Sopenharmony_ci
3448c2ecf20Sopenharmony_ci	if (finfo->num_planes > 1)
3458c2ecf20Sopenharmony_ci		stride |= ((state->src_w >> 16) * finfo->cpp[1] / finfo->hsub)
3468c2ecf20Sopenharmony_ci			<< JZ_IPU_UV_STRIDE_U_LSB;
3478c2ecf20Sopenharmony_ci
3488c2ecf20Sopenharmony_ci	regmap_write(ipu->map, JZ_REG_IPU_UV_STRIDE, stride);
3498c2ecf20Sopenharmony_ci
3508c2ecf20Sopenharmony_ci	stride = ((state->src_w >> 16) * finfo->cpp[0]) << JZ_IPU_Y_STRIDE_Y_LSB;
3518c2ecf20Sopenharmony_ci	regmap_write(ipu->map, JZ_REG_IPU_Y_STRIDE, stride);
3528c2ecf20Sopenharmony_ci
3538c2ecf20Sopenharmony_ci	regmap_write(ipu->map, JZ_REG_IPU_IN_GS,
3548c2ecf20Sopenharmony_ci		     (stride << JZ_IPU_IN_GS_W_LSB) |
3558c2ecf20Sopenharmony_ci		     ((state->src_h >> 16) << JZ_IPU_IN_GS_H_LSB));
3568c2ecf20Sopenharmony_ci
3578c2ecf20Sopenharmony_ci	switch (finfo->format) {
3588c2ecf20Sopenharmony_ci	case DRM_FORMAT_XRGB1555:
3598c2ecf20Sopenharmony_ci		format = JZ_IPU_D_FMT_IN_FMT_RGB555 |
3608c2ecf20Sopenharmony_ci			JZ_IPU_D_FMT_RGB_OUT_OFT_RGB;
3618c2ecf20Sopenharmony_ci		break;
3628c2ecf20Sopenharmony_ci	case DRM_FORMAT_XBGR1555:
3638c2ecf20Sopenharmony_ci		format = JZ_IPU_D_FMT_IN_FMT_RGB555 |
3648c2ecf20Sopenharmony_ci			JZ_IPU_D_FMT_RGB_OUT_OFT_BGR;
3658c2ecf20Sopenharmony_ci		break;
3668c2ecf20Sopenharmony_ci	case DRM_FORMAT_RGB565:
3678c2ecf20Sopenharmony_ci		format = JZ_IPU_D_FMT_IN_FMT_RGB565 |
3688c2ecf20Sopenharmony_ci			JZ_IPU_D_FMT_RGB_OUT_OFT_RGB;
3698c2ecf20Sopenharmony_ci		break;
3708c2ecf20Sopenharmony_ci	case DRM_FORMAT_BGR565:
3718c2ecf20Sopenharmony_ci		format = JZ_IPU_D_FMT_IN_FMT_RGB565 |
3728c2ecf20Sopenharmony_ci			JZ_IPU_D_FMT_RGB_OUT_OFT_BGR;
3738c2ecf20Sopenharmony_ci		break;
3748c2ecf20Sopenharmony_ci	case DRM_FORMAT_XRGB8888:
3758c2ecf20Sopenharmony_ci	case DRM_FORMAT_XYUV8888:
3768c2ecf20Sopenharmony_ci		format = JZ_IPU_D_FMT_IN_FMT_RGB888 |
3778c2ecf20Sopenharmony_ci			JZ_IPU_D_FMT_RGB_OUT_OFT_RGB;
3788c2ecf20Sopenharmony_ci		break;
3798c2ecf20Sopenharmony_ci	case DRM_FORMAT_XBGR8888:
3808c2ecf20Sopenharmony_ci		format = JZ_IPU_D_FMT_IN_FMT_RGB888 |
3818c2ecf20Sopenharmony_ci			JZ_IPU_D_FMT_RGB_OUT_OFT_BGR;
3828c2ecf20Sopenharmony_ci		break;
3838c2ecf20Sopenharmony_ci	case DRM_FORMAT_YUYV:
3848c2ecf20Sopenharmony_ci		format = JZ_IPU_D_FMT_IN_FMT_YUV422 |
3858c2ecf20Sopenharmony_ci			JZ_IPU_D_FMT_YUV_VY1UY0;
3868c2ecf20Sopenharmony_ci		break;
3878c2ecf20Sopenharmony_ci	case DRM_FORMAT_YVYU:
3888c2ecf20Sopenharmony_ci		format = JZ_IPU_D_FMT_IN_FMT_YUV422 |
3898c2ecf20Sopenharmony_ci			JZ_IPU_D_FMT_YUV_UY1VY0;
3908c2ecf20Sopenharmony_ci		break;
3918c2ecf20Sopenharmony_ci	case DRM_FORMAT_UYVY:
3928c2ecf20Sopenharmony_ci		format = JZ_IPU_D_FMT_IN_FMT_YUV422 |
3938c2ecf20Sopenharmony_ci			JZ_IPU_D_FMT_YUV_Y1VY0U;
3948c2ecf20Sopenharmony_ci		break;
3958c2ecf20Sopenharmony_ci	case DRM_FORMAT_VYUY:
3968c2ecf20Sopenharmony_ci		format = JZ_IPU_D_FMT_IN_FMT_YUV422 |
3978c2ecf20Sopenharmony_ci			JZ_IPU_D_FMT_YUV_Y1UY0V;
3988c2ecf20Sopenharmony_ci		break;
3998c2ecf20Sopenharmony_ci	case DRM_FORMAT_YUV411:
4008c2ecf20Sopenharmony_ci		format = JZ_IPU_D_FMT_IN_FMT_YUV411;
4018c2ecf20Sopenharmony_ci		break;
4028c2ecf20Sopenharmony_ci	case DRM_FORMAT_YUV420:
4038c2ecf20Sopenharmony_ci		format = JZ_IPU_D_FMT_IN_FMT_YUV420;
4048c2ecf20Sopenharmony_ci		break;
4058c2ecf20Sopenharmony_ci	case DRM_FORMAT_YUV422:
4068c2ecf20Sopenharmony_ci		format = JZ_IPU_D_FMT_IN_FMT_YUV422;
4078c2ecf20Sopenharmony_ci		break;
4088c2ecf20Sopenharmony_ci	case DRM_FORMAT_YUV444:
4098c2ecf20Sopenharmony_ci		format = JZ_IPU_D_FMT_IN_FMT_YUV444;
4108c2ecf20Sopenharmony_ci		break;
4118c2ecf20Sopenharmony_ci	default:
4128c2ecf20Sopenharmony_ci		WARN_ONCE(1, "Unsupported format");
4138c2ecf20Sopenharmony_ci		break;
4148c2ecf20Sopenharmony_ci	}
4158c2ecf20Sopenharmony_ci
4168c2ecf20Sopenharmony_ci	/* Fix output to RGB888 */
4178c2ecf20Sopenharmony_ci	format |= JZ_IPU_D_FMT_OUT_FMT_RGB888;
4188c2ecf20Sopenharmony_ci
4198c2ecf20Sopenharmony_ci	/* Set pixel format */
4208c2ecf20Sopenharmony_ci	regmap_write(ipu->map, JZ_REG_IPU_D_FMT, format);
4218c2ecf20Sopenharmony_ci
4228c2ecf20Sopenharmony_ci	/* Set the output height/width/stride */
4238c2ecf20Sopenharmony_ci	regmap_write(ipu->map, JZ_REG_IPU_OUT_GS,
4248c2ecf20Sopenharmony_ci		     ((state->crtc_w * 4) << JZ_IPU_OUT_GS_W_LSB)
4258c2ecf20Sopenharmony_ci		     | state->crtc_h << JZ_IPU_OUT_GS_H_LSB);
4268c2ecf20Sopenharmony_ci	regmap_write(ipu->map, JZ_REG_IPU_OUT_STRIDE, state->crtc_w * 4);
4278c2ecf20Sopenharmony_ci
4288c2ecf20Sopenharmony_ci	if (finfo->is_yuv) {
4298c2ecf20Sopenharmony_ci		regmap_set_bits(ipu->map, JZ_REG_IPU_CTRL, JZ_IPU_CTRL_CSC_EN);
4308c2ecf20Sopenharmony_ci
4318c2ecf20Sopenharmony_ci		/*
4328c2ecf20Sopenharmony_ci		 * Offsets for Chroma/Luma.
4338c2ecf20Sopenharmony_ci		 * y = source Y - LUMA,
4348c2ecf20Sopenharmony_ci		 * u = source Cb - CHROMA,
4358c2ecf20Sopenharmony_ci		 * v = source Cr - CHROMA
4368c2ecf20Sopenharmony_ci		 */
4378c2ecf20Sopenharmony_ci		regmap_write(ipu->map, JZ_REG_IPU_CSC_OFFSET,
4388c2ecf20Sopenharmony_ci			     128 << JZ_IPU_CSC_OFFSET_CHROMA_LSB |
4398c2ecf20Sopenharmony_ci			     0 << JZ_IPU_CSC_OFFSET_LUMA_LSB);
4408c2ecf20Sopenharmony_ci
4418c2ecf20Sopenharmony_ci		/*
4428c2ecf20Sopenharmony_ci		 * YUV422 to RGB conversion table.
4438c2ecf20Sopenharmony_ci		 * R = C0 / 0x400 * y + C1 / 0x400 * v
4448c2ecf20Sopenharmony_ci		 * G = C0 / 0x400 * y - C2 / 0x400 * u - C3 / 0x400 * v
4458c2ecf20Sopenharmony_ci		 * B = C0 / 0x400 * y + C4 / 0x400 * u
4468c2ecf20Sopenharmony_ci		 */
4478c2ecf20Sopenharmony_ci		regmap_write(ipu->map, JZ_REG_IPU_CSC_C0_COEF, 0x4a8);
4488c2ecf20Sopenharmony_ci		regmap_write(ipu->map, JZ_REG_IPU_CSC_C1_COEF, 0x662);
4498c2ecf20Sopenharmony_ci		regmap_write(ipu->map, JZ_REG_IPU_CSC_C2_COEF, 0x191);
4508c2ecf20Sopenharmony_ci		regmap_write(ipu->map, JZ_REG_IPU_CSC_C3_COEF, 0x341);
4518c2ecf20Sopenharmony_ci		regmap_write(ipu->map, JZ_REG_IPU_CSC_C4_COEF, 0x811);
4528c2ecf20Sopenharmony_ci	}
4538c2ecf20Sopenharmony_ci
4548c2ecf20Sopenharmony_ci	ctrl = 0;
4558c2ecf20Sopenharmony_ci
4568c2ecf20Sopenharmony_ci	/*
4578c2ecf20Sopenharmony_ci	 * Must set ZOOM_SEL before programming bicubic LUTs.
4588c2ecf20Sopenharmony_ci	 * If the IPU supports bicubic, we enable it unconditionally, since it
4598c2ecf20Sopenharmony_ci	 * can do anything bilinear can and more.
4608c2ecf20Sopenharmony_ci	 */
4618c2ecf20Sopenharmony_ci	if (ipu->soc_info->has_bicubic)
4628c2ecf20Sopenharmony_ci		ctrl |= JZ_IPU_CTRL_ZOOM_SEL;
4638c2ecf20Sopenharmony_ci
4648c2ecf20Sopenharmony_ci	upscaling_w = ipu->num_w > ipu->denom_w;
4658c2ecf20Sopenharmony_ci	if (upscaling_w)
4668c2ecf20Sopenharmony_ci		ctrl |= JZ_IPU_CTRL_HSCALE;
4678c2ecf20Sopenharmony_ci
4688c2ecf20Sopenharmony_ci	if (ipu->num_w != 1 || ipu->denom_w != 1) {
4698c2ecf20Sopenharmony_ci		if (!ipu->soc_info->has_bicubic && !upscaling_w)
4708c2ecf20Sopenharmony_ci			coef_index |= (ipu->denom_w - 1) << 16;
4718c2ecf20Sopenharmony_ci		else
4728c2ecf20Sopenharmony_ci			coef_index |= (ipu->num_w - 1) << 16;
4738c2ecf20Sopenharmony_ci		ctrl |= JZ_IPU_CTRL_HRSZ_EN;
4748c2ecf20Sopenharmony_ci	}
4758c2ecf20Sopenharmony_ci
4768c2ecf20Sopenharmony_ci	upscaling_h = ipu->num_h > ipu->denom_h;
4778c2ecf20Sopenharmony_ci	if (upscaling_h)
4788c2ecf20Sopenharmony_ci		ctrl |= JZ_IPU_CTRL_VSCALE;
4798c2ecf20Sopenharmony_ci
4808c2ecf20Sopenharmony_ci	if (ipu->num_h != 1 || ipu->denom_h != 1) {
4818c2ecf20Sopenharmony_ci		if (!ipu->soc_info->has_bicubic && !upscaling_h)
4828c2ecf20Sopenharmony_ci			coef_index |= ipu->denom_h - 1;
4838c2ecf20Sopenharmony_ci		else
4848c2ecf20Sopenharmony_ci			coef_index |= ipu->num_h - 1;
4858c2ecf20Sopenharmony_ci		ctrl |= JZ_IPU_CTRL_VRSZ_EN;
4868c2ecf20Sopenharmony_ci	}
4878c2ecf20Sopenharmony_ci
4888c2ecf20Sopenharmony_ci	regmap_update_bits(ipu->map, JZ_REG_IPU_CTRL, JZ_IPU_CTRL_ZOOM_SEL |
4898c2ecf20Sopenharmony_ci			   JZ_IPU_CTRL_HRSZ_EN | JZ_IPU_CTRL_VRSZ_EN |
4908c2ecf20Sopenharmony_ci			   JZ_IPU_CTRL_HSCALE | JZ_IPU_CTRL_VSCALE, ctrl);
4918c2ecf20Sopenharmony_ci
4928c2ecf20Sopenharmony_ci	/* Set the LUT index register */
4938c2ecf20Sopenharmony_ci	regmap_write(ipu->map, JZ_REG_IPU_RSZ_COEF_INDEX, coef_index);
4948c2ecf20Sopenharmony_ci
4958c2ecf20Sopenharmony_ci	if (ipu->num_w != 1 || ipu->denom_w != 1)
4968c2ecf20Sopenharmony_ci		ingenic_ipu_set_coefs(ipu, JZ_REG_IPU_HRSZ_COEF_LUT,
4978c2ecf20Sopenharmony_ci				      ipu->num_w, ipu->denom_w);
4988c2ecf20Sopenharmony_ci
4998c2ecf20Sopenharmony_ci	if (ipu->num_h != 1 || ipu->denom_h != 1)
5008c2ecf20Sopenharmony_ci		ingenic_ipu_set_coefs(ipu, JZ_REG_IPU_VRSZ_COEF_LUT,
5018c2ecf20Sopenharmony_ci				      ipu->num_h, ipu->denom_h);
5028c2ecf20Sopenharmony_ci
5038c2ecf20Sopenharmony_ci	/* Clear STATUS register */
5048c2ecf20Sopenharmony_ci	regmap_write(ipu->map, JZ_REG_IPU_STATUS, 0);
5058c2ecf20Sopenharmony_ci
5068c2ecf20Sopenharmony_ci	/* Start IPU */
5078c2ecf20Sopenharmony_ci	regmap_set_bits(ipu->map, JZ_REG_IPU_CTRL,
5088c2ecf20Sopenharmony_ci			JZ_IPU_CTRL_RUN | JZ_IPU_CTRL_FM_IRQ_EN);
5098c2ecf20Sopenharmony_ci
5108c2ecf20Sopenharmony_ci	dev_dbg(ipu->dev, "Scaling %ux%u to %ux%u (%u:%u horiz, %u:%u vert)\n",
5118c2ecf20Sopenharmony_ci		state->src_w >> 16, state->src_h >> 16,
5128c2ecf20Sopenharmony_ci		state->crtc_w, state->crtc_h,
5138c2ecf20Sopenharmony_ci		ipu->num_w, ipu->denom_w, ipu->num_h, ipu->denom_h);
5148c2ecf20Sopenharmony_ci}
5158c2ecf20Sopenharmony_ci
5168c2ecf20Sopenharmony_cistatic int ingenic_ipu_plane_atomic_check(struct drm_plane *plane,
5178c2ecf20Sopenharmony_ci					  struct drm_plane_state *state)
5188c2ecf20Sopenharmony_ci{
5198c2ecf20Sopenharmony_ci	unsigned int num_w, denom_w, num_h, denom_h, xres, yres;
5208c2ecf20Sopenharmony_ci	struct ingenic_ipu *ipu = plane_to_ingenic_ipu(plane);
5218c2ecf20Sopenharmony_ci	struct drm_crtc *crtc = state->crtc ?: plane->state->crtc;
5228c2ecf20Sopenharmony_ci	struct drm_crtc_state *crtc_state;
5238c2ecf20Sopenharmony_ci
5248c2ecf20Sopenharmony_ci	if (!crtc)
5258c2ecf20Sopenharmony_ci		return 0;
5268c2ecf20Sopenharmony_ci
5278c2ecf20Sopenharmony_ci	crtc_state = drm_atomic_get_existing_crtc_state(state->state, crtc);
5288c2ecf20Sopenharmony_ci	if (WARN_ON(!crtc_state))
5298c2ecf20Sopenharmony_ci		return -EINVAL;
5308c2ecf20Sopenharmony_ci
5318c2ecf20Sopenharmony_ci	/* Request a full modeset if we are enabling or disabling the IPU. */
5328c2ecf20Sopenharmony_ci	if (!plane->state->crtc ^ !state->crtc)
5338c2ecf20Sopenharmony_ci		crtc_state->mode_changed = true;
5348c2ecf20Sopenharmony_ci
5358c2ecf20Sopenharmony_ci	if (!state->crtc ||
5368c2ecf20Sopenharmony_ci	    !crtc_state->mode.hdisplay || !crtc_state->mode.vdisplay)
5378c2ecf20Sopenharmony_ci		return 0;
5388c2ecf20Sopenharmony_ci
5398c2ecf20Sopenharmony_ci	/* Plane must be fully visible */
5408c2ecf20Sopenharmony_ci	if (state->crtc_x < 0 || state->crtc_y < 0 ||
5418c2ecf20Sopenharmony_ci	    state->crtc_x + state->crtc_w > crtc_state->mode.hdisplay ||
5428c2ecf20Sopenharmony_ci	    state->crtc_y + state->crtc_h > crtc_state->mode.vdisplay)
5438c2ecf20Sopenharmony_ci		return -EINVAL;
5448c2ecf20Sopenharmony_ci
5458c2ecf20Sopenharmony_ci	/* Minimum size is 4x4 */
5468c2ecf20Sopenharmony_ci	if ((state->src_w >> 16) < 4 || (state->src_h >> 16) < 4)
5478c2ecf20Sopenharmony_ci		return -EINVAL;
5488c2ecf20Sopenharmony_ci
5498c2ecf20Sopenharmony_ci	/* Input and output lines must have an even number of pixels. */
5508c2ecf20Sopenharmony_ci	if (((state->src_w >> 16) & 1) || (state->crtc_w & 1))
5518c2ecf20Sopenharmony_ci		return -EINVAL;
5528c2ecf20Sopenharmony_ci
5538c2ecf20Sopenharmony_ci	if (!osd_changed(state, plane->state))
5548c2ecf20Sopenharmony_ci		return 0;
5558c2ecf20Sopenharmony_ci
5568c2ecf20Sopenharmony_ci	crtc_state->mode_changed = true;
5578c2ecf20Sopenharmony_ci
5588c2ecf20Sopenharmony_ci	xres = state->src_w >> 16;
5598c2ecf20Sopenharmony_ci	yres = state->src_h >> 16;
5608c2ecf20Sopenharmony_ci
5618c2ecf20Sopenharmony_ci	/* Adjust the coefficients until we find a valid configuration */
5628c2ecf20Sopenharmony_ci	for (denom_w = xres, num_w = state->crtc_w;
5638c2ecf20Sopenharmony_ci	     num_w <= crtc_state->mode.hdisplay; num_w++)
5648c2ecf20Sopenharmony_ci		if (!reduce_fraction(&num_w, &denom_w))
5658c2ecf20Sopenharmony_ci			break;
5668c2ecf20Sopenharmony_ci	if (num_w > crtc_state->mode.hdisplay)
5678c2ecf20Sopenharmony_ci		return -EINVAL;
5688c2ecf20Sopenharmony_ci
5698c2ecf20Sopenharmony_ci	for (denom_h = yres, num_h = state->crtc_h;
5708c2ecf20Sopenharmony_ci	     num_h <= crtc_state->mode.vdisplay; num_h++)
5718c2ecf20Sopenharmony_ci		if (!reduce_fraction(&num_h, &denom_h))
5728c2ecf20Sopenharmony_ci			break;
5738c2ecf20Sopenharmony_ci	if (num_h > crtc_state->mode.vdisplay)
5748c2ecf20Sopenharmony_ci		return -EINVAL;
5758c2ecf20Sopenharmony_ci
5768c2ecf20Sopenharmony_ci	ipu->num_w = num_w;
5778c2ecf20Sopenharmony_ci	ipu->num_h = num_h;
5788c2ecf20Sopenharmony_ci	ipu->denom_w = denom_w;
5798c2ecf20Sopenharmony_ci	ipu->denom_h = denom_h;
5808c2ecf20Sopenharmony_ci
5818c2ecf20Sopenharmony_ci	return 0;
5828c2ecf20Sopenharmony_ci}
5838c2ecf20Sopenharmony_ci
5848c2ecf20Sopenharmony_cistatic void ingenic_ipu_plane_atomic_disable(struct drm_plane *plane,
5858c2ecf20Sopenharmony_ci					     struct drm_plane_state *old_state)
5868c2ecf20Sopenharmony_ci{
5878c2ecf20Sopenharmony_ci	struct ingenic_ipu *ipu = plane_to_ingenic_ipu(plane);
5888c2ecf20Sopenharmony_ci
5898c2ecf20Sopenharmony_ci	regmap_set_bits(ipu->map, JZ_REG_IPU_CTRL, JZ_IPU_CTRL_STOP);
5908c2ecf20Sopenharmony_ci	regmap_clear_bits(ipu->map, JZ_REG_IPU_CTRL, JZ_IPU_CTRL_CHIP_EN);
5918c2ecf20Sopenharmony_ci
5928c2ecf20Sopenharmony_ci	ingenic_drm_plane_disable(ipu->master, plane);
5938c2ecf20Sopenharmony_ci
5948c2ecf20Sopenharmony_ci	if (ipu->clk_enabled) {
5958c2ecf20Sopenharmony_ci		clk_disable(ipu->clk);
5968c2ecf20Sopenharmony_ci		ipu->clk_enabled = false;
5978c2ecf20Sopenharmony_ci	}
5988c2ecf20Sopenharmony_ci}
5998c2ecf20Sopenharmony_ci
6008c2ecf20Sopenharmony_cistatic const struct drm_plane_helper_funcs ingenic_ipu_plane_helper_funcs = {
6018c2ecf20Sopenharmony_ci	.atomic_update		= ingenic_ipu_plane_atomic_update,
6028c2ecf20Sopenharmony_ci	.atomic_check		= ingenic_ipu_plane_atomic_check,
6038c2ecf20Sopenharmony_ci	.atomic_disable		= ingenic_ipu_plane_atomic_disable,
6048c2ecf20Sopenharmony_ci	.prepare_fb		= drm_gem_fb_prepare_fb,
6058c2ecf20Sopenharmony_ci};
6068c2ecf20Sopenharmony_ci
6078c2ecf20Sopenharmony_cistatic int
6088c2ecf20Sopenharmony_ciingenic_ipu_plane_atomic_get_property(struct drm_plane *plane,
6098c2ecf20Sopenharmony_ci				      const struct drm_plane_state *state,
6108c2ecf20Sopenharmony_ci				      struct drm_property *property, u64 *val)
6118c2ecf20Sopenharmony_ci{
6128c2ecf20Sopenharmony_ci	struct ingenic_ipu *ipu = plane_to_ingenic_ipu(plane);
6138c2ecf20Sopenharmony_ci
6148c2ecf20Sopenharmony_ci	if (property != ipu->sharpness_prop)
6158c2ecf20Sopenharmony_ci		return -EINVAL;
6168c2ecf20Sopenharmony_ci
6178c2ecf20Sopenharmony_ci	*val = ipu->sharpness;
6188c2ecf20Sopenharmony_ci
6198c2ecf20Sopenharmony_ci	return 0;
6208c2ecf20Sopenharmony_ci}
6218c2ecf20Sopenharmony_ci
6228c2ecf20Sopenharmony_cistatic int
6238c2ecf20Sopenharmony_ciingenic_ipu_plane_atomic_set_property(struct drm_plane *plane,
6248c2ecf20Sopenharmony_ci				      struct drm_plane_state *state,
6258c2ecf20Sopenharmony_ci				      struct drm_property *property, u64 val)
6268c2ecf20Sopenharmony_ci{
6278c2ecf20Sopenharmony_ci	struct ingenic_ipu *ipu = plane_to_ingenic_ipu(plane);
6288c2ecf20Sopenharmony_ci	struct drm_crtc_state *crtc_state;
6298c2ecf20Sopenharmony_ci
6308c2ecf20Sopenharmony_ci	if (property != ipu->sharpness_prop)
6318c2ecf20Sopenharmony_ci		return -EINVAL;
6328c2ecf20Sopenharmony_ci
6338c2ecf20Sopenharmony_ci	ipu->sharpness = val;
6348c2ecf20Sopenharmony_ci
6358c2ecf20Sopenharmony_ci	if (state->crtc) {
6368c2ecf20Sopenharmony_ci		crtc_state = drm_atomic_get_existing_crtc_state(state->state, state->crtc);
6378c2ecf20Sopenharmony_ci		if (WARN_ON(!crtc_state))
6388c2ecf20Sopenharmony_ci			return -EINVAL;
6398c2ecf20Sopenharmony_ci
6408c2ecf20Sopenharmony_ci		crtc_state->mode_changed = true;
6418c2ecf20Sopenharmony_ci	}
6428c2ecf20Sopenharmony_ci
6438c2ecf20Sopenharmony_ci	return 0;
6448c2ecf20Sopenharmony_ci}
6458c2ecf20Sopenharmony_ci
6468c2ecf20Sopenharmony_cistatic const struct drm_plane_funcs ingenic_ipu_plane_funcs = {
6478c2ecf20Sopenharmony_ci	.update_plane		= drm_atomic_helper_update_plane,
6488c2ecf20Sopenharmony_ci	.disable_plane		= drm_atomic_helper_disable_plane,
6498c2ecf20Sopenharmony_ci	.reset			= drm_atomic_helper_plane_reset,
6508c2ecf20Sopenharmony_ci	.destroy		= drm_plane_cleanup,
6518c2ecf20Sopenharmony_ci
6528c2ecf20Sopenharmony_ci	.atomic_duplicate_state	= drm_atomic_helper_plane_duplicate_state,
6538c2ecf20Sopenharmony_ci	.atomic_destroy_state	= drm_atomic_helper_plane_destroy_state,
6548c2ecf20Sopenharmony_ci
6558c2ecf20Sopenharmony_ci	.atomic_get_property	= ingenic_ipu_plane_atomic_get_property,
6568c2ecf20Sopenharmony_ci	.atomic_set_property	= ingenic_ipu_plane_atomic_set_property,
6578c2ecf20Sopenharmony_ci};
6588c2ecf20Sopenharmony_ci
6598c2ecf20Sopenharmony_cistatic irqreturn_t ingenic_ipu_irq_handler(int irq, void *arg)
6608c2ecf20Sopenharmony_ci{
6618c2ecf20Sopenharmony_ci	struct ingenic_ipu *ipu = arg;
6628c2ecf20Sopenharmony_ci	struct drm_crtc *crtc = drm_crtc_from_index(ipu->drm, 0);
6638c2ecf20Sopenharmony_ci	unsigned int dummy;
6648c2ecf20Sopenharmony_ci
6658c2ecf20Sopenharmony_ci	/* dummy read allows CPU to reconfigure IPU */
6668c2ecf20Sopenharmony_ci	if (ipu->soc_info->manual_restart)
6678c2ecf20Sopenharmony_ci		regmap_read(ipu->map, JZ_REG_IPU_STATUS, &dummy);
6688c2ecf20Sopenharmony_ci
6698c2ecf20Sopenharmony_ci	/* ACK interrupt */
6708c2ecf20Sopenharmony_ci	regmap_write(ipu->map, JZ_REG_IPU_STATUS, 0);
6718c2ecf20Sopenharmony_ci
6728c2ecf20Sopenharmony_ci	/* Set previously cached addresses */
6738c2ecf20Sopenharmony_ci	regmap_write(ipu->map, JZ_REG_IPU_Y_ADDR, ipu->addr_y);
6748c2ecf20Sopenharmony_ci	regmap_write(ipu->map, JZ_REG_IPU_U_ADDR, ipu->addr_u);
6758c2ecf20Sopenharmony_ci	regmap_write(ipu->map, JZ_REG_IPU_V_ADDR, ipu->addr_v);
6768c2ecf20Sopenharmony_ci
6778c2ecf20Sopenharmony_ci	/* Run IPU for the new frame */
6788c2ecf20Sopenharmony_ci	if (ipu->soc_info->manual_restart)
6798c2ecf20Sopenharmony_ci		regmap_set_bits(ipu->map, JZ_REG_IPU_CTRL, JZ_IPU_CTRL_RUN);
6808c2ecf20Sopenharmony_ci
6818c2ecf20Sopenharmony_ci	drm_crtc_handle_vblank(crtc);
6828c2ecf20Sopenharmony_ci
6838c2ecf20Sopenharmony_ci	return IRQ_HANDLED;
6848c2ecf20Sopenharmony_ci}
6858c2ecf20Sopenharmony_ci
6868c2ecf20Sopenharmony_cistatic const struct regmap_config ingenic_ipu_regmap_config = {
6878c2ecf20Sopenharmony_ci	.reg_bits = 32,
6888c2ecf20Sopenharmony_ci	.val_bits = 32,
6898c2ecf20Sopenharmony_ci	.reg_stride = 4,
6908c2ecf20Sopenharmony_ci
6918c2ecf20Sopenharmony_ci	.max_register = JZ_REG_IPU_OUT_PHY_T_ADDR,
6928c2ecf20Sopenharmony_ci};
6938c2ecf20Sopenharmony_ci
6948c2ecf20Sopenharmony_cistatic int ingenic_ipu_bind(struct device *dev, struct device *master, void *d)
6958c2ecf20Sopenharmony_ci{
6968c2ecf20Sopenharmony_ci	struct platform_device *pdev = to_platform_device(dev);
6978c2ecf20Sopenharmony_ci	const struct soc_info *soc_info;
6988c2ecf20Sopenharmony_ci	struct drm_device *drm = d;
6998c2ecf20Sopenharmony_ci	struct drm_plane *plane;
7008c2ecf20Sopenharmony_ci	struct ingenic_ipu *ipu;
7018c2ecf20Sopenharmony_ci	void __iomem *base;
7028c2ecf20Sopenharmony_ci	unsigned int sharpness_max;
7038c2ecf20Sopenharmony_ci	int err, irq;
7048c2ecf20Sopenharmony_ci
7058c2ecf20Sopenharmony_ci	ipu = devm_kzalloc(dev, sizeof(*ipu), GFP_KERNEL);
7068c2ecf20Sopenharmony_ci	if (!ipu)
7078c2ecf20Sopenharmony_ci		return -ENOMEM;
7088c2ecf20Sopenharmony_ci
7098c2ecf20Sopenharmony_ci	soc_info = of_device_get_match_data(dev);
7108c2ecf20Sopenharmony_ci	if (!soc_info) {
7118c2ecf20Sopenharmony_ci		dev_err(dev, "Missing platform data\n");
7128c2ecf20Sopenharmony_ci		return -EINVAL;
7138c2ecf20Sopenharmony_ci	}
7148c2ecf20Sopenharmony_ci
7158c2ecf20Sopenharmony_ci	ipu->dev = dev;
7168c2ecf20Sopenharmony_ci	ipu->drm = drm;
7178c2ecf20Sopenharmony_ci	ipu->master = master;
7188c2ecf20Sopenharmony_ci	ipu->soc_info = soc_info;
7198c2ecf20Sopenharmony_ci
7208c2ecf20Sopenharmony_ci	base = devm_platform_ioremap_resource(pdev, 0);
7218c2ecf20Sopenharmony_ci	if (IS_ERR(base)) {
7228c2ecf20Sopenharmony_ci		dev_err(dev, "Failed to get memory resource\n");
7238c2ecf20Sopenharmony_ci		return PTR_ERR(base);
7248c2ecf20Sopenharmony_ci	}
7258c2ecf20Sopenharmony_ci
7268c2ecf20Sopenharmony_ci	ipu->map = devm_regmap_init_mmio(dev, base, &ingenic_ipu_regmap_config);
7278c2ecf20Sopenharmony_ci	if (IS_ERR(ipu->map)) {
7288c2ecf20Sopenharmony_ci		dev_err(dev, "Failed to create regmap\n");
7298c2ecf20Sopenharmony_ci		return PTR_ERR(ipu->map);
7308c2ecf20Sopenharmony_ci	}
7318c2ecf20Sopenharmony_ci
7328c2ecf20Sopenharmony_ci	irq = platform_get_irq(pdev, 0);
7338c2ecf20Sopenharmony_ci	if (irq < 0)
7348c2ecf20Sopenharmony_ci		return irq;
7358c2ecf20Sopenharmony_ci
7368c2ecf20Sopenharmony_ci	ipu->clk = devm_clk_get(dev, "ipu");
7378c2ecf20Sopenharmony_ci	if (IS_ERR(ipu->clk)) {
7388c2ecf20Sopenharmony_ci		dev_err(dev, "Failed to get pixel clock\n");
7398c2ecf20Sopenharmony_ci		return PTR_ERR(ipu->clk);
7408c2ecf20Sopenharmony_ci	}
7418c2ecf20Sopenharmony_ci
7428c2ecf20Sopenharmony_ci	err = devm_request_irq(dev, irq, ingenic_ipu_irq_handler, 0,
7438c2ecf20Sopenharmony_ci			       dev_name(dev), ipu);
7448c2ecf20Sopenharmony_ci	if (err) {
7458c2ecf20Sopenharmony_ci		dev_err(dev, "Unable to request IRQ\n");
7468c2ecf20Sopenharmony_ci		return err;
7478c2ecf20Sopenharmony_ci	}
7488c2ecf20Sopenharmony_ci
7498c2ecf20Sopenharmony_ci	plane = &ipu->plane;
7508c2ecf20Sopenharmony_ci	dev_set_drvdata(dev, plane);
7518c2ecf20Sopenharmony_ci
7528c2ecf20Sopenharmony_ci	drm_plane_helper_add(plane, &ingenic_ipu_plane_helper_funcs);
7538c2ecf20Sopenharmony_ci
7548c2ecf20Sopenharmony_ci	err = drm_universal_plane_init(drm, plane, 1, &ingenic_ipu_plane_funcs,
7558c2ecf20Sopenharmony_ci				       soc_info->formats, soc_info->num_formats,
7568c2ecf20Sopenharmony_ci				       NULL, DRM_PLANE_TYPE_OVERLAY, NULL);
7578c2ecf20Sopenharmony_ci	if (err) {
7588c2ecf20Sopenharmony_ci		dev_err(dev, "Failed to init plane: %i\n", err);
7598c2ecf20Sopenharmony_ci		return err;
7608c2ecf20Sopenharmony_ci	}
7618c2ecf20Sopenharmony_ci
7628c2ecf20Sopenharmony_ci	/*
7638c2ecf20Sopenharmony_ci	 * Sharpness settings range is [0,32]
7648c2ecf20Sopenharmony_ci	 * 0       : nearest-neighbor
7658c2ecf20Sopenharmony_ci	 * 1       : bilinear
7668c2ecf20Sopenharmony_ci	 * 2 .. 32 : bicubic (translated to sharpness factor -0.25 .. -4.0)
7678c2ecf20Sopenharmony_ci	 */
7688c2ecf20Sopenharmony_ci	sharpness_max = soc_info->has_bicubic ? 32 : 1;
7698c2ecf20Sopenharmony_ci	ipu->sharpness_prop = drm_property_create_range(drm, 0, "sharpness",
7708c2ecf20Sopenharmony_ci							0, sharpness_max);
7718c2ecf20Sopenharmony_ci	if (!ipu->sharpness_prop) {
7728c2ecf20Sopenharmony_ci		dev_err(dev, "Unable to create sharpness property\n");
7738c2ecf20Sopenharmony_ci		return -ENOMEM;
7748c2ecf20Sopenharmony_ci	}
7758c2ecf20Sopenharmony_ci
7768c2ecf20Sopenharmony_ci	/* Default sharpness factor: -0.125 * 8 = -1.0 */
7778c2ecf20Sopenharmony_ci	ipu->sharpness = soc_info->has_bicubic ? 8 : 1;
7788c2ecf20Sopenharmony_ci	drm_object_attach_property(&plane->base, ipu->sharpness_prop,
7798c2ecf20Sopenharmony_ci				   ipu->sharpness);
7808c2ecf20Sopenharmony_ci
7818c2ecf20Sopenharmony_ci	err = clk_prepare(ipu->clk);
7828c2ecf20Sopenharmony_ci	if (err) {
7838c2ecf20Sopenharmony_ci		dev_err(dev, "Unable to prepare clock\n");
7848c2ecf20Sopenharmony_ci		return err;
7858c2ecf20Sopenharmony_ci	}
7868c2ecf20Sopenharmony_ci
7878c2ecf20Sopenharmony_ci	return 0;
7888c2ecf20Sopenharmony_ci}
7898c2ecf20Sopenharmony_ci
7908c2ecf20Sopenharmony_cistatic void ingenic_ipu_unbind(struct device *dev,
7918c2ecf20Sopenharmony_ci			       struct device *master, void *d)
7928c2ecf20Sopenharmony_ci{
7938c2ecf20Sopenharmony_ci	struct ingenic_ipu *ipu = dev_get_drvdata(dev);
7948c2ecf20Sopenharmony_ci
7958c2ecf20Sopenharmony_ci	clk_unprepare(ipu->clk);
7968c2ecf20Sopenharmony_ci}
7978c2ecf20Sopenharmony_ci
7988c2ecf20Sopenharmony_cistatic const struct component_ops ingenic_ipu_ops = {
7998c2ecf20Sopenharmony_ci	.bind = ingenic_ipu_bind,
8008c2ecf20Sopenharmony_ci	.unbind = ingenic_ipu_unbind,
8018c2ecf20Sopenharmony_ci};
8028c2ecf20Sopenharmony_ci
8038c2ecf20Sopenharmony_cistatic int ingenic_ipu_probe(struct platform_device *pdev)
8048c2ecf20Sopenharmony_ci{
8058c2ecf20Sopenharmony_ci	return component_add(&pdev->dev, &ingenic_ipu_ops);
8068c2ecf20Sopenharmony_ci}
8078c2ecf20Sopenharmony_ci
8088c2ecf20Sopenharmony_cistatic int ingenic_ipu_remove(struct platform_device *pdev)
8098c2ecf20Sopenharmony_ci{
8108c2ecf20Sopenharmony_ci	component_del(&pdev->dev, &ingenic_ipu_ops);
8118c2ecf20Sopenharmony_ci	return 0;
8128c2ecf20Sopenharmony_ci}
8138c2ecf20Sopenharmony_ci
8148c2ecf20Sopenharmony_cistatic const u32 jz4725b_ipu_formats[] = {
8158c2ecf20Sopenharmony_ci	/*
8168c2ecf20Sopenharmony_ci	 * While officially supported, packed YUV 4:2:2 formats can cause
8178c2ecf20Sopenharmony_ci	 * random hardware crashes on JZ4725B under certain circumstances.
8188c2ecf20Sopenharmony_ci	 * It seems to happen with some specific resize ratios.
8198c2ecf20Sopenharmony_ci	 * Until a proper workaround or fix is found, disable these formats.
8208c2ecf20Sopenharmony_ci	DRM_FORMAT_YUYV,
8218c2ecf20Sopenharmony_ci	DRM_FORMAT_YVYU,
8228c2ecf20Sopenharmony_ci	DRM_FORMAT_UYVY,
8238c2ecf20Sopenharmony_ci	DRM_FORMAT_VYUY,
8248c2ecf20Sopenharmony_ci	*/
8258c2ecf20Sopenharmony_ci	DRM_FORMAT_YUV411,
8268c2ecf20Sopenharmony_ci	DRM_FORMAT_YUV420,
8278c2ecf20Sopenharmony_ci	DRM_FORMAT_YUV422,
8288c2ecf20Sopenharmony_ci	DRM_FORMAT_YUV444,
8298c2ecf20Sopenharmony_ci};
8308c2ecf20Sopenharmony_ci
8318c2ecf20Sopenharmony_cistatic const struct soc_info jz4725b_soc_info = {
8328c2ecf20Sopenharmony_ci	.formats	= jz4725b_ipu_formats,
8338c2ecf20Sopenharmony_ci	.num_formats	= ARRAY_SIZE(jz4725b_ipu_formats),
8348c2ecf20Sopenharmony_ci	.has_bicubic	= false,
8358c2ecf20Sopenharmony_ci	.manual_restart	= true,
8368c2ecf20Sopenharmony_ci	.set_coefs	= jz4725b_set_coefs,
8378c2ecf20Sopenharmony_ci};
8388c2ecf20Sopenharmony_ci
8398c2ecf20Sopenharmony_cistatic const u32 jz4760_ipu_formats[] = {
8408c2ecf20Sopenharmony_ci	DRM_FORMAT_XRGB1555,
8418c2ecf20Sopenharmony_ci	DRM_FORMAT_XBGR1555,
8428c2ecf20Sopenharmony_ci	DRM_FORMAT_RGB565,
8438c2ecf20Sopenharmony_ci	DRM_FORMAT_BGR565,
8448c2ecf20Sopenharmony_ci	DRM_FORMAT_XRGB8888,
8458c2ecf20Sopenharmony_ci	DRM_FORMAT_XBGR8888,
8468c2ecf20Sopenharmony_ci	DRM_FORMAT_YUYV,
8478c2ecf20Sopenharmony_ci	DRM_FORMAT_YVYU,
8488c2ecf20Sopenharmony_ci	DRM_FORMAT_UYVY,
8498c2ecf20Sopenharmony_ci	DRM_FORMAT_VYUY,
8508c2ecf20Sopenharmony_ci	DRM_FORMAT_YUV411,
8518c2ecf20Sopenharmony_ci	DRM_FORMAT_YUV420,
8528c2ecf20Sopenharmony_ci	DRM_FORMAT_YUV422,
8538c2ecf20Sopenharmony_ci	DRM_FORMAT_YUV444,
8548c2ecf20Sopenharmony_ci	DRM_FORMAT_XYUV8888,
8558c2ecf20Sopenharmony_ci};
8568c2ecf20Sopenharmony_ci
8578c2ecf20Sopenharmony_cistatic const struct soc_info jz4760_soc_info = {
8588c2ecf20Sopenharmony_ci	.formats	= jz4760_ipu_formats,
8598c2ecf20Sopenharmony_ci	.num_formats	= ARRAY_SIZE(jz4760_ipu_formats),
8608c2ecf20Sopenharmony_ci	.has_bicubic	= true,
8618c2ecf20Sopenharmony_ci	.manual_restart	= false,
8628c2ecf20Sopenharmony_ci	.set_coefs	= jz4760_set_coefs,
8638c2ecf20Sopenharmony_ci};
8648c2ecf20Sopenharmony_ci
8658c2ecf20Sopenharmony_cistatic const struct of_device_id ingenic_ipu_of_match[] = {
8668c2ecf20Sopenharmony_ci	{ .compatible = "ingenic,jz4725b-ipu", .data = &jz4725b_soc_info },
8678c2ecf20Sopenharmony_ci	{ .compatible = "ingenic,jz4760-ipu", .data = &jz4760_soc_info },
8688c2ecf20Sopenharmony_ci	{ /* sentinel */ },
8698c2ecf20Sopenharmony_ci};
8708c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, ingenic_ipu_of_match);
8718c2ecf20Sopenharmony_ci
8728c2ecf20Sopenharmony_cistatic struct platform_driver ingenic_ipu_driver = {
8738c2ecf20Sopenharmony_ci	.driver = {
8748c2ecf20Sopenharmony_ci		.name = "ingenic-ipu",
8758c2ecf20Sopenharmony_ci		.of_match_table = ingenic_ipu_of_match,
8768c2ecf20Sopenharmony_ci	},
8778c2ecf20Sopenharmony_ci	.probe = ingenic_ipu_probe,
8788c2ecf20Sopenharmony_ci	.remove = ingenic_ipu_remove,
8798c2ecf20Sopenharmony_ci};
8808c2ecf20Sopenharmony_ci
8818c2ecf20Sopenharmony_cistruct platform_driver *ingenic_ipu_driver_ptr = &ingenic_ipu_driver;
882