1fc1acf31SPaul Cercueil // SPDX-License-Identifier: GPL-2.0 2fc1acf31SPaul Cercueil // 3fc1acf31SPaul Cercueil // Ingenic JZ47xx IPU driver 4fc1acf31SPaul Cercueil // 5fc1acf31SPaul Cercueil // Copyright (C) 2020, Paul Cercueil <paul@crapouillou.net> 6fc1acf31SPaul Cercueil // Copyright (C) 2020, Daniel Silsby <dansilsby@gmail.com> 7fc1acf31SPaul Cercueil 8fc1acf31SPaul Cercueil #include "ingenic-drm.h" 9fc1acf31SPaul Cercueil #include "ingenic-ipu.h" 10fc1acf31SPaul Cercueil 11fc1acf31SPaul Cercueil #include <linux/clk.h> 12fc1acf31SPaul Cercueil #include <linux/component.h> 13fc1acf31SPaul Cercueil #include <linux/gcd.h> 14fc1acf31SPaul Cercueil #include <linux/interrupt.h> 15fc1acf31SPaul Cercueil #include <linux/module.h> 16fc1acf31SPaul Cercueil #include <linux/of.h> 17fc1acf31SPaul Cercueil #include <linux/of_device.h> 18fc1acf31SPaul Cercueil #include <linux/regmap.h> 19fc1acf31SPaul Cercueil #include <linux/time.h> 20fc1acf31SPaul Cercueil 21fc1acf31SPaul Cercueil #include <drm/drm_atomic.h> 22fc1acf31SPaul Cercueil #include <drm/drm_atomic_helper.h> 23fc1acf31SPaul Cercueil #include <drm/drm_drv.h> 24fc1acf31SPaul Cercueil #include <drm/drm_fb_cma_helper.h> 25fc1acf31SPaul Cercueil #include <drm/drm_fourcc.h> 26820c1707SThomas Zimmermann #include <drm/drm_gem_atomic_helper.h> 27fc1acf31SPaul Cercueil #include <drm/drm_plane.h> 28fc1acf31SPaul Cercueil #include <drm/drm_plane_helper.h> 29fc1acf31SPaul Cercueil #include <drm/drm_property.h> 30fc1acf31SPaul Cercueil #include <drm/drm_vblank.h> 31fc1acf31SPaul Cercueil 32fc1acf31SPaul Cercueil struct ingenic_ipu; 33fc1acf31SPaul Cercueil 34fc1acf31SPaul Cercueil struct soc_info { 35fc1acf31SPaul Cercueil const u32 *formats; 36fc1acf31SPaul Cercueil size_t num_formats; 37fc1acf31SPaul Cercueil bool has_bicubic; 383debcdf0SPaul Cercueil bool manual_restart; 39fc1acf31SPaul Cercueil 40fc1acf31SPaul Cercueil void (*set_coefs)(struct ingenic_ipu *ipu, unsigned int reg, 41fc1acf31SPaul Cercueil unsigned int sharpness, bool downscale, 42fc1acf31SPaul Cercueil unsigned int weight, unsigned int offset); 43fc1acf31SPaul Cercueil }; 44fc1acf31SPaul Cercueil 45fc1acf31SPaul Cercueil struct ingenic_ipu { 46fc1acf31SPaul Cercueil struct drm_plane plane; 47fc1acf31SPaul Cercueil struct drm_device *drm; 48fc1acf31SPaul Cercueil struct device *dev, *master; 49fc1acf31SPaul Cercueil struct regmap *map; 50fc1acf31SPaul Cercueil struct clk *clk; 51fc1acf31SPaul Cercueil const struct soc_info *soc_info; 5238ee474fSPaul Cercueil bool clk_enabled; 53fc1acf31SPaul Cercueil 54fc1acf31SPaul Cercueil unsigned int num_w, num_h, denom_w, denom_h; 55fc1acf31SPaul Cercueil 56fc1acf31SPaul Cercueil dma_addr_t addr_y, addr_u, addr_v; 57fc1acf31SPaul Cercueil 58fc1acf31SPaul Cercueil struct drm_property *sharpness_prop; 59fc1acf31SPaul Cercueil unsigned int sharpness; 60fc1acf31SPaul Cercueil }; 61fc1acf31SPaul Cercueil 62fc1acf31SPaul Cercueil /* Signed 15.16 fixed-point math (for bicubic scaling coefficients) */ 63fc1acf31SPaul Cercueil #define I2F(i) ((s32)(i) * 65536) 64fc1acf31SPaul Cercueil #define F2I(f) ((f) / 65536) 65fc1acf31SPaul Cercueil #define FMUL(fa, fb) ((s32)(((s64)(fa) * (s64)(fb)) / 65536)) 66fc1acf31SPaul Cercueil #define SHARPNESS_INCR (I2F(-1) / 8) 67fc1acf31SPaul Cercueil 68fc1acf31SPaul Cercueil static inline struct ingenic_ipu *plane_to_ingenic_ipu(struct drm_plane *plane) 69fc1acf31SPaul Cercueil { 70fc1acf31SPaul Cercueil return container_of(plane, struct ingenic_ipu, plane); 71fc1acf31SPaul Cercueil } 72fc1acf31SPaul Cercueil 73fc1acf31SPaul Cercueil /* 74fc1acf31SPaul Cercueil * Apply conventional cubic convolution kernel. Both parameters 75fc1acf31SPaul Cercueil * and return value are 15.16 signed fixed-point. 76fc1acf31SPaul Cercueil * 77fc1acf31SPaul Cercueil * @f_a: Sharpness factor, typically in range [-4.0, -0.25]. 78fc1acf31SPaul Cercueil * A larger magnitude increases perceived sharpness, but going past 79fc1acf31SPaul Cercueil * -2.0 might cause ringing artifacts to outweigh any improvement. 80fc1acf31SPaul Cercueil * Nice values on a 320x240 LCD are between -0.75 and -2.0. 81fc1acf31SPaul Cercueil * 82fc1acf31SPaul Cercueil * @f_x: Absolute distance in pixels from 'pixel 0' sample position 83fc1acf31SPaul Cercueil * along horizontal (or vertical) source axis. Range is [0, +2.0]. 84fc1acf31SPaul Cercueil * 85fc1acf31SPaul Cercueil * returns: Weight of this pixel within 4-pixel sample group. Range is 86fc1acf31SPaul Cercueil * [-2.0, +2.0]. For moderate (i.e. > -3.0) sharpness factors, 87fc1acf31SPaul Cercueil * range is within [-1.0, +1.0]. 88fc1acf31SPaul Cercueil */ 89fc1acf31SPaul Cercueil static inline s32 cubic_conv(s32 f_a, s32 f_x) 90fc1acf31SPaul Cercueil { 91fc1acf31SPaul Cercueil const s32 f_1 = I2F(1); 92fc1acf31SPaul Cercueil const s32 f_2 = I2F(2); 93fc1acf31SPaul Cercueil const s32 f_3 = I2F(3); 94fc1acf31SPaul Cercueil const s32 f_4 = I2F(4); 95fc1acf31SPaul Cercueil const s32 f_x2 = FMUL(f_x, f_x); 96fc1acf31SPaul Cercueil const s32 f_x3 = FMUL(f_x, f_x2); 97fc1acf31SPaul Cercueil 98fc1acf31SPaul Cercueil if (f_x <= f_1) 99fc1acf31SPaul Cercueil return FMUL((f_a + f_2), f_x3) - FMUL((f_a + f_3), f_x2) + f_1; 100fc1acf31SPaul Cercueil else if (f_x <= f_2) 101fc1acf31SPaul Cercueil return FMUL(f_a, (f_x3 - 5 * f_x2 + 8 * f_x - f_4)); 102fc1acf31SPaul Cercueil else 103fc1acf31SPaul Cercueil return 0; 104fc1acf31SPaul Cercueil } 105fc1acf31SPaul Cercueil 106fc1acf31SPaul Cercueil /* 107fc1acf31SPaul Cercueil * On entry, "weight" is a coefficient suitable for bilinear mode, 108fc1acf31SPaul Cercueil * which is converted to a set of four suitable for bicubic mode. 109fc1acf31SPaul Cercueil * 110fc1acf31SPaul Cercueil * "weight 512" means all of pixel 0; 111fc1acf31SPaul Cercueil * "weight 256" means half of pixel 0 and half of pixel 1; 112fc1acf31SPaul Cercueil * "weight 0" means all of pixel 1; 113fc1acf31SPaul Cercueil * 114fc1acf31SPaul Cercueil * "offset" is increment to next source pixel sample location. 115fc1acf31SPaul Cercueil */ 116fc1acf31SPaul Cercueil static void jz4760_set_coefs(struct ingenic_ipu *ipu, unsigned int reg, 117fc1acf31SPaul Cercueil unsigned int sharpness, bool downscale, 118fc1acf31SPaul Cercueil unsigned int weight, unsigned int offset) 119fc1acf31SPaul Cercueil { 120fc1acf31SPaul Cercueil u32 val; 121fc1acf31SPaul Cercueil s32 w0, w1, w2, w3; /* Pixel weights at X (or Y) offsets -1,0,1,2 */ 122fc1acf31SPaul Cercueil 123fc1acf31SPaul Cercueil weight = clamp_val(weight, 0, 512); 124fc1acf31SPaul Cercueil 125fc1acf31SPaul Cercueil if (sharpness < 2) { 126fc1acf31SPaul Cercueil /* 127fc1acf31SPaul Cercueil * When sharpness setting is 0, emulate nearest-neighbor. 128fc1acf31SPaul Cercueil * When sharpness setting is 1, emulate bilinear. 129fc1acf31SPaul Cercueil */ 130fc1acf31SPaul Cercueil 131fc1acf31SPaul Cercueil if (sharpness == 0) 132fc1acf31SPaul Cercueil weight = weight >= 256 ? 512 : 0; 133fc1acf31SPaul Cercueil w0 = 0; 134fc1acf31SPaul Cercueil w1 = weight; 135fc1acf31SPaul Cercueil w2 = 512 - weight; 136fc1acf31SPaul Cercueil w3 = 0; 137fc1acf31SPaul Cercueil } else { 138fc1acf31SPaul Cercueil const s32 f_a = SHARPNESS_INCR * sharpness; 139fc1acf31SPaul Cercueil const s32 f_h = I2F(1) / 2; /* Round up 0.5 */ 140fc1acf31SPaul Cercueil 141fc1acf31SPaul Cercueil /* 142fc1acf31SPaul Cercueil * Note that always rounding towards +infinity here is intended. 143fc1acf31SPaul Cercueil * The resulting coefficients match a round-to-nearest-int 144fc1acf31SPaul Cercueil * double floating-point implementation. 145fc1acf31SPaul Cercueil */ 146fc1acf31SPaul Cercueil 147fc1acf31SPaul Cercueil weight = 512 - weight; 148fc1acf31SPaul Cercueil w0 = F2I(f_h + 512 * cubic_conv(f_a, I2F(512 + weight) / 512)); 149fc1acf31SPaul Cercueil w1 = F2I(f_h + 512 * cubic_conv(f_a, I2F(0 + weight) / 512)); 150fc1acf31SPaul Cercueil w2 = F2I(f_h + 512 * cubic_conv(f_a, I2F(512 - weight) / 512)); 151fc1acf31SPaul Cercueil w3 = F2I(f_h + 512 * cubic_conv(f_a, I2F(1024 - weight) / 512)); 152fc1acf31SPaul Cercueil w0 = clamp_val(w0, -1024, 1023); 153fc1acf31SPaul Cercueil w1 = clamp_val(w1, -1024, 1023); 154fc1acf31SPaul Cercueil w2 = clamp_val(w2, -1024, 1023); 155fc1acf31SPaul Cercueil w3 = clamp_val(w3, -1024, 1023); 156fc1acf31SPaul Cercueil } 157fc1acf31SPaul Cercueil 158fc1acf31SPaul Cercueil val = ((w1 & JZ4760_IPU_RSZ_COEF_MASK) << JZ4760_IPU_RSZ_COEF31_LSB) | 159fc1acf31SPaul Cercueil ((w0 & JZ4760_IPU_RSZ_COEF_MASK) << JZ4760_IPU_RSZ_COEF20_LSB); 160fc1acf31SPaul Cercueil regmap_write(ipu->map, reg, val); 161fc1acf31SPaul Cercueil 162fc1acf31SPaul Cercueil val = ((w3 & JZ4760_IPU_RSZ_COEF_MASK) << JZ4760_IPU_RSZ_COEF31_LSB) | 163fc1acf31SPaul Cercueil ((w2 & JZ4760_IPU_RSZ_COEF_MASK) << JZ4760_IPU_RSZ_COEF20_LSB) | 164fc1acf31SPaul Cercueil ((offset & JZ4760_IPU_RSZ_OFFSET_MASK) << JZ4760_IPU_RSZ_OFFSET_LSB); 165fc1acf31SPaul Cercueil regmap_write(ipu->map, reg, val); 166fc1acf31SPaul Cercueil } 167fc1acf31SPaul Cercueil 168fc1acf31SPaul Cercueil static void jz4725b_set_coefs(struct ingenic_ipu *ipu, unsigned int reg, 169fc1acf31SPaul Cercueil unsigned int sharpness, bool downscale, 170fc1acf31SPaul Cercueil unsigned int weight, unsigned int offset) 171fc1acf31SPaul Cercueil { 172fc1acf31SPaul Cercueil u32 val = JZ4725B_IPU_RSZ_LUT_OUT_EN; 173fc1acf31SPaul Cercueil unsigned int i; 174fc1acf31SPaul Cercueil 175fc1acf31SPaul Cercueil weight = clamp_val(weight, 0, 512); 176fc1acf31SPaul Cercueil 177fc1acf31SPaul Cercueil if (sharpness == 0) 178fc1acf31SPaul Cercueil weight = weight >= 256 ? 512 : 0; 179fc1acf31SPaul Cercueil 180fc1acf31SPaul Cercueil val |= (weight & JZ4725B_IPU_RSZ_LUT_COEF_MASK) << JZ4725B_IPU_RSZ_LUT_COEF_LSB; 181fc1acf31SPaul Cercueil if (downscale || !!offset) 182fc1acf31SPaul Cercueil val |= JZ4725B_IPU_RSZ_LUT_IN_EN; 183fc1acf31SPaul Cercueil 184fc1acf31SPaul Cercueil regmap_write(ipu->map, reg, val); 185fc1acf31SPaul Cercueil 186fc1acf31SPaul Cercueil if (downscale) { 187fc1acf31SPaul Cercueil for (i = 1; i < offset; i++) 188fc1acf31SPaul Cercueil regmap_write(ipu->map, reg, JZ4725B_IPU_RSZ_LUT_IN_EN); 189fc1acf31SPaul Cercueil } 190fc1acf31SPaul Cercueil } 191fc1acf31SPaul Cercueil 192fc1acf31SPaul Cercueil static void ingenic_ipu_set_downscale_coefs(struct ingenic_ipu *ipu, 193fc1acf31SPaul Cercueil unsigned int reg, 194fc1acf31SPaul Cercueil unsigned int num, 195fc1acf31SPaul Cercueil unsigned int denom) 196fc1acf31SPaul Cercueil { 197fc1acf31SPaul Cercueil unsigned int i, offset, weight, weight_num = denom; 198fc1acf31SPaul Cercueil 199fc1acf31SPaul Cercueil for (i = 0; i < num; i++) { 200fc1acf31SPaul Cercueil weight_num = num + (weight_num - num) % (num * 2); 201fc1acf31SPaul Cercueil weight = 512 - 512 * (weight_num - num) / (num * 2); 202fc1acf31SPaul Cercueil weight_num += denom * 2; 203fc1acf31SPaul Cercueil offset = (weight_num - num) / (num * 2); 204fc1acf31SPaul Cercueil 205fc1acf31SPaul Cercueil ipu->soc_info->set_coefs(ipu, reg, ipu->sharpness, 206fc1acf31SPaul Cercueil true, weight, offset); 207fc1acf31SPaul Cercueil } 208fc1acf31SPaul Cercueil } 209fc1acf31SPaul Cercueil 210fc1acf31SPaul Cercueil static void ingenic_ipu_set_integer_upscale_coefs(struct ingenic_ipu *ipu, 211fc1acf31SPaul Cercueil unsigned int reg, 212fc1acf31SPaul Cercueil unsigned int num) 213fc1acf31SPaul Cercueil { 214fc1acf31SPaul Cercueil /* 215fc1acf31SPaul Cercueil * Force nearest-neighbor scaling and use simple math when upscaling 216fc1acf31SPaul Cercueil * by an integer ratio. It looks better, and fixes a few problem cases. 217fc1acf31SPaul Cercueil */ 218fc1acf31SPaul Cercueil unsigned int i; 219fc1acf31SPaul Cercueil 220fc1acf31SPaul Cercueil for (i = 0; i < num; i++) 221fc1acf31SPaul Cercueil ipu->soc_info->set_coefs(ipu, reg, 0, false, 512, i == num - 1); 222fc1acf31SPaul Cercueil } 223fc1acf31SPaul Cercueil 224fc1acf31SPaul Cercueil static void ingenic_ipu_set_upscale_coefs(struct ingenic_ipu *ipu, 225fc1acf31SPaul Cercueil unsigned int reg, 226fc1acf31SPaul Cercueil unsigned int num, 227fc1acf31SPaul Cercueil unsigned int denom) 228fc1acf31SPaul Cercueil { 229fc1acf31SPaul Cercueil unsigned int i, offset, weight, weight_num = 0; 230fc1acf31SPaul Cercueil 231fc1acf31SPaul Cercueil for (i = 0; i < num; i++) { 232fc1acf31SPaul Cercueil weight = 512 - 512 * weight_num / num; 233fc1acf31SPaul Cercueil weight_num += denom; 234fc1acf31SPaul Cercueil offset = weight_num >= num; 235fc1acf31SPaul Cercueil 236fc1acf31SPaul Cercueil if (offset) 237fc1acf31SPaul Cercueil weight_num -= num; 238fc1acf31SPaul Cercueil 239fc1acf31SPaul Cercueil ipu->soc_info->set_coefs(ipu, reg, ipu->sharpness, 240fc1acf31SPaul Cercueil false, weight, offset); 241fc1acf31SPaul Cercueil } 242fc1acf31SPaul Cercueil } 243fc1acf31SPaul Cercueil 244fc1acf31SPaul Cercueil static void ingenic_ipu_set_coefs(struct ingenic_ipu *ipu, unsigned int reg, 245fc1acf31SPaul Cercueil unsigned int num, unsigned int denom) 246fc1acf31SPaul Cercueil { 247fc1acf31SPaul Cercueil /* Begin programming the LUT */ 248fc1acf31SPaul Cercueil regmap_write(ipu->map, reg, -1); 249fc1acf31SPaul Cercueil 250fc1acf31SPaul Cercueil if (denom > num) 251fc1acf31SPaul Cercueil ingenic_ipu_set_downscale_coefs(ipu, reg, num, denom); 252fc1acf31SPaul Cercueil else if (denom == 1) 253fc1acf31SPaul Cercueil ingenic_ipu_set_integer_upscale_coefs(ipu, reg, num); 254fc1acf31SPaul Cercueil else 255fc1acf31SPaul Cercueil ingenic_ipu_set_upscale_coefs(ipu, reg, num, denom); 256fc1acf31SPaul Cercueil } 257fc1acf31SPaul Cercueil 258fc1acf31SPaul Cercueil static int reduce_fraction(unsigned int *num, unsigned int *denom) 259fc1acf31SPaul Cercueil { 260fc1acf31SPaul Cercueil unsigned long d = gcd(*num, *denom); 261fc1acf31SPaul Cercueil 262fc1acf31SPaul Cercueil /* The scaling table has only 31 entries */ 263fc1acf31SPaul Cercueil if (*num > 31 * d) 264fc1acf31SPaul Cercueil return -EINVAL; 265fc1acf31SPaul Cercueil 266fc1acf31SPaul Cercueil *num /= d; 267fc1acf31SPaul Cercueil *denom /= d; 268fc1acf31SPaul Cercueil return 0; 269fc1acf31SPaul Cercueil } 270fc1acf31SPaul Cercueil 271fc1acf31SPaul Cercueil static inline bool osd_changed(struct drm_plane_state *state, 272fc1acf31SPaul Cercueil struct drm_plane_state *oldstate) 273fc1acf31SPaul Cercueil { 274fc1acf31SPaul Cercueil return state->src_x != oldstate->src_x || 275fc1acf31SPaul Cercueil state->src_y != oldstate->src_y || 276fc1acf31SPaul Cercueil state->src_w != oldstate->src_w || 277fc1acf31SPaul Cercueil state->src_h != oldstate->src_h || 278fc1acf31SPaul Cercueil state->crtc_x != oldstate->crtc_x || 279fc1acf31SPaul Cercueil state->crtc_y != oldstate->crtc_y || 280fc1acf31SPaul Cercueil state->crtc_w != oldstate->crtc_w || 281fc1acf31SPaul Cercueil state->crtc_h != oldstate->crtc_h; 282fc1acf31SPaul Cercueil } 283fc1acf31SPaul Cercueil 284fc1acf31SPaul Cercueil static void ingenic_ipu_plane_atomic_update(struct drm_plane *plane, 285fc1acf31SPaul Cercueil struct drm_plane_state *oldstate) 286fc1acf31SPaul Cercueil { 287fc1acf31SPaul Cercueil struct ingenic_ipu *ipu = plane_to_ingenic_ipu(plane); 288fc1acf31SPaul Cercueil struct drm_plane_state *state = plane->state; 289fc1acf31SPaul Cercueil const struct drm_format_info *finfo; 290fc1acf31SPaul Cercueil u32 ctrl, stride = 0, coef_index = 0, format = 0; 291fc1acf31SPaul Cercueil bool needs_modeset, upscaling_w, upscaling_h; 29238ee474fSPaul Cercueil int err; 293fc1acf31SPaul Cercueil 294fc1acf31SPaul Cercueil if (!state || !state->fb) 295fc1acf31SPaul Cercueil return; 296fc1acf31SPaul Cercueil 297fc1acf31SPaul Cercueil finfo = drm_format_info(state->fb->format->format); 298fc1acf31SPaul Cercueil 29938ee474fSPaul Cercueil if (!ipu->clk_enabled) { 30038ee474fSPaul Cercueil err = clk_enable(ipu->clk); 30138ee474fSPaul Cercueil if (err) { 30238ee474fSPaul Cercueil dev_err(ipu->dev, "Unable to enable clock: %d\n", err); 30338ee474fSPaul Cercueil return; 30438ee474fSPaul Cercueil } 30538ee474fSPaul Cercueil 30638ee474fSPaul Cercueil ipu->clk_enabled = true; 30738ee474fSPaul Cercueil } 30838ee474fSPaul Cercueil 309fc1acf31SPaul Cercueil /* Reset all the registers if needed */ 310fc1acf31SPaul Cercueil needs_modeset = drm_atomic_crtc_needs_modeset(state->crtc->state); 311fc1acf31SPaul Cercueil if (needs_modeset) { 312fc1acf31SPaul Cercueil regmap_set_bits(ipu->map, JZ_REG_IPU_CTRL, JZ_IPU_CTRL_RST); 313fc1acf31SPaul Cercueil 314fc1acf31SPaul Cercueil /* Enable the chip */ 315fc1acf31SPaul Cercueil regmap_set_bits(ipu->map, JZ_REG_IPU_CTRL, 316fc1acf31SPaul Cercueil JZ_IPU_CTRL_CHIP_EN | JZ_IPU_CTRL_LCDC_SEL); 317fc1acf31SPaul Cercueil } 318fc1acf31SPaul Cercueil 319fc1acf31SPaul Cercueil /* New addresses will be committed in vblank handler... */ 320fc1acf31SPaul Cercueil ipu->addr_y = drm_fb_cma_get_gem_addr(state->fb, state, 0); 321fc1acf31SPaul Cercueil if (finfo->num_planes > 1) 322fc1acf31SPaul Cercueil ipu->addr_u = drm_fb_cma_get_gem_addr(state->fb, state, 1); 323fc1acf31SPaul Cercueil if (finfo->num_planes > 2) 324fc1acf31SPaul Cercueil ipu->addr_v = drm_fb_cma_get_gem_addr(state->fb, state, 2); 325fc1acf31SPaul Cercueil 326fc1acf31SPaul Cercueil if (!needs_modeset) 327fc1acf31SPaul Cercueil return; 328fc1acf31SPaul Cercueil 329fc1acf31SPaul Cercueil /* Or right here if we're doing a full modeset. */ 330fc1acf31SPaul Cercueil regmap_write(ipu->map, JZ_REG_IPU_Y_ADDR, ipu->addr_y); 331fc1acf31SPaul Cercueil regmap_write(ipu->map, JZ_REG_IPU_U_ADDR, ipu->addr_u); 332fc1acf31SPaul Cercueil regmap_write(ipu->map, JZ_REG_IPU_V_ADDR, ipu->addr_v); 333fc1acf31SPaul Cercueil 334fc1acf31SPaul Cercueil if (finfo->num_planes == 1) 335fc1acf31SPaul Cercueil regmap_set_bits(ipu->map, JZ_REG_IPU_CTRL, JZ_IPU_CTRL_SPKG_SEL); 336fc1acf31SPaul Cercueil 337fc1acf31SPaul Cercueil ingenic_drm_plane_config(ipu->master, plane, DRM_FORMAT_XRGB8888); 338fc1acf31SPaul Cercueil 339fc1acf31SPaul Cercueil /* Set the input height/width/strides */ 340fc1acf31SPaul Cercueil if (finfo->num_planes > 2) 341fc1acf31SPaul Cercueil stride = ((state->src_w >> 16) * finfo->cpp[2] / finfo->hsub) 342fc1acf31SPaul Cercueil << JZ_IPU_UV_STRIDE_V_LSB; 343fc1acf31SPaul Cercueil 344fc1acf31SPaul Cercueil if (finfo->num_planes > 1) 345fc1acf31SPaul Cercueil stride |= ((state->src_w >> 16) * finfo->cpp[1] / finfo->hsub) 346fc1acf31SPaul Cercueil << JZ_IPU_UV_STRIDE_U_LSB; 347fc1acf31SPaul Cercueil 348fc1acf31SPaul Cercueil regmap_write(ipu->map, JZ_REG_IPU_UV_STRIDE, stride); 349fc1acf31SPaul Cercueil 350fc1acf31SPaul Cercueil stride = ((state->src_w >> 16) * finfo->cpp[0]) << JZ_IPU_Y_STRIDE_Y_LSB; 351fc1acf31SPaul Cercueil regmap_write(ipu->map, JZ_REG_IPU_Y_STRIDE, stride); 352fc1acf31SPaul Cercueil 353fc1acf31SPaul Cercueil regmap_write(ipu->map, JZ_REG_IPU_IN_GS, 354fc1acf31SPaul Cercueil (stride << JZ_IPU_IN_GS_W_LSB) | 355fc1acf31SPaul Cercueil ((state->src_h >> 16) << JZ_IPU_IN_GS_H_LSB)); 356fc1acf31SPaul Cercueil 357fc1acf31SPaul Cercueil switch (finfo->format) { 358fc1acf31SPaul Cercueil case DRM_FORMAT_XRGB1555: 359fc1acf31SPaul Cercueil format = JZ_IPU_D_FMT_IN_FMT_RGB555 | 360fc1acf31SPaul Cercueil JZ_IPU_D_FMT_RGB_OUT_OFT_RGB; 361fc1acf31SPaul Cercueil break; 362fc1acf31SPaul Cercueil case DRM_FORMAT_XBGR1555: 363fc1acf31SPaul Cercueil format = JZ_IPU_D_FMT_IN_FMT_RGB555 | 364fc1acf31SPaul Cercueil JZ_IPU_D_FMT_RGB_OUT_OFT_BGR; 365fc1acf31SPaul Cercueil break; 366fc1acf31SPaul Cercueil case DRM_FORMAT_RGB565: 367fc1acf31SPaul Cercueil format = JZ_IPU_D_FMT_IN_FMT_RGB565 | 368fc1acf31SPaul Cercueil JZ_IPU_D_FMT_RGB_OUT_OFT_RGB; 369fc1acf31SPaul Cercueil break; 370fc1acf31SPaul Cercueil case DRM_FORMAT_BGR565: 371fc1acf31SPaul Cercueil format = JZ_IPU_D_FMT_IN_FMT_RGB565 | 372fc1acf31SPaul Cercueil JZ_IPU_D_FMT_RGB_OUT_OFT_BGR; 373fc1acf31SPaul Cercueil break; 374fc1acf31SPaul Cercueil case DRM_FORMAT_XRGB8888: 375fc1acf31SPaul Cercueil case DRM_FORMAT_XYUV8888: 376fc1acf31SPaul Cercueil format = JZ_IPU_D_FMT_IN_FMT_RGB888 | 377fc1acf31SPaul Cercueil JZ_IPU_D_FMT_RGB_OUT_OFT_RGB; 378fc1acf31SPaul Cercueil break; 379fc1acf31SPaul Cercueil case DRM_FORMAT_XBGR8888: 380fc1acf31SPaul Cercueil format = JZ_IPU_D_FMT_IN_FMT_RGB888 | 381fc1acf31SPaul Cercueil JZ_IPU_D_FMT_RGB_OUT_OFT_BGR; 382fc1acf31SPaul Cercueil break; 383fc1acf31SPaul Cercueil case DRM_FORMAT_YUYV: 384fc1acf31SPaul Cercueil format = JZ_IPU_D_FMT_IN_FMT_YUV422 | 385fc1acf31SPaul Cercueil JZ_IPU_D_FMT_YUV_VY1UY0; 386fc1acf31SPaul Cercueil break; 387fc1acf31SPaul Cercueil case DRM_FORMAT_YVYU: 388fc1acf31SPaul Cercueil format = JZ_IPU_D_FMT_IN_FMT_YUV422 | 389fc1acf31SPaul Cercueil JZ_IPU_D_FMT_YUV_UY1VY0; 390fc1acf31SPaul Cercueil break; 391fc1acf31SPaul Cercueil case DRM_FORMAT_UYVY: 392fc1acf31SPaul Cercueil format = JZ_IPU_D_FMT_IN_FMT_YUV422 | 393fc1acf31SPaul Cercueil JZ_IPU_D_FMT_YUV_Y1VY0U; 394fc1acf31SPaul Cercueil break; 395fc1acf31SPaul Cercueil case DRM_FORMAT_VYUY: 396fc1acf31SPaul Cercueil format = JZ_IPU_D_FMT_IN_FMT_YUV422 | 397fc1acf31SPaul Cercueil JZ_IPU_D_FMT_YUV_Y1UY0V; 398fc1acf31SPaul Cercueil break; 399fc1acf31SPaul Cercueil case DRM_FORMAT_YUV411: 400fc1acf31SPaul Cercueil format = JZ_IPU_D_FMT_IN_FMT_YUV411; 401fc1acf31SPaul Cercueil break; 402fc1acf31SPaul Cercueil case DRM_FORMAT_YUV420: 403fc1acf31SPaul Cercueil format = JZ_IPU_D_FMT_IN_FMT_YUV420; 404fc1acf31SPaul Cercueil break; 405fc1acf31SPaul Cercueil case DRM_FORMAT_YUV422: 406fc1acf31SPaul Cercueil format = JZ_IPU_D_FMT_IN_FMT_YUV422; 407fc1acf31SPaul Cercueil break; 408fc1acf31SPaul Cercueil case DRM_FORMAT_YUV444: 409fc1acf31SPaul Cercueil format = JZ_IPU_D_FMT_IN_FMT_YUV444; 410fc1acf31SPaul Cercueil break; 411fc1acf31SPaul Cercueil default: 412fc1acf31SPaul Cercueil WARN_ONCE(1, "Unsupported format"); 413fc1acf31SPaul Cercueil break; 414fc1acf31SPaul Cercueil } 415fc1acf31SPaul Cercueil 416fc1acf31SPaul Cercueil /* Fix output to RGB888 */ 417fc1acf31SPaul Cercueil format |= JZ_IPU_D_FMT_OUT_FMT_RGB888; 418fc1acf31SPaul Cercueil 419fc1acf31SPaul Cercueil /* Set pixel format */ 420fc1acf31SPaul Cercueil regmap_write(ipu->map, JZ_REG_IPU_D_FMT, format); 421fc1acf31SPaul Cercueil 422fc1acf31SPaul Cercueil /* Set the output height/width/stride */ 423fc1acf31SPaul Cercueil regmap_write(ipu->map, JZ_REG_IPU_OUT_GS, 424fc1acf31SPaul Cercueil ((state->crtc_w * 4) << JZ_IPU_OUT_GS_W_LSB) 425fc1acf31SPaul Cercueil | state->crtc_h << JZ_IPU_OUT_GS_H_LSB); 426fc1acf31SPaul Cercueil regmap_write(ipu->map, JZ_REG_IPU_OUT_STRIDE, state->crtc_w * 4); 427fc1acf31SPaul Cercueil 428fc1acf31SPaul Cercueil if (finfo->is_yuv) { 429fc1acf31SPaul Cercueil regmap_set_bits(ipu->map, JZ_REG_IPU_CTRL, JZ_IPU_CTRL_CSC_EN); 430fc1acf31SPaul Cercueil 431fc1acf31SPaul Cercueil /* 432fc1acf31SPaul Cercueil * Offsets for Chroma/Luma. 433fc1acf31SPaul Cercueil * y = source Y - LUMA, 434fc1acf31SPaul Cercueil * u = source Cb - CHROMA, 435fc1acf31SPaul Cercueil * v = source Cr - CHROMA 436fc1acf31SPaul Cercueil */ 437fc1acf31SPaul Cercueil regmap_write(ipu->map, JZ_REG_IPU_CSC_OFFSET, 438fc1acf31SPaul Cercueil 128 << JZ_IPU_CSC_OFFSET_CHROMA_LSB | 439fc1acf31SPaul Cercueil 0 << JZ_IPU_CSC_OFFSET_LUMA_LSB); 440fc1acf31SPaul Cercueil 441fc1acf31SPaul Cercueil /* 442fc1acf31SPaul Cercueil * YUV422 to RGB conversion table. 443fc1acf31SPaul Cercueil * R = C0 / 0x400 * y + C1 / 0x400 * v 444fc1acf31SPaul Cercueil * G = C0 / 0x400 * y - C2 / 0x400 * u - C3 / 0x400 * v 445fc1acf31SPaul Cercueil * B = C0 / 0x400 * y + C4 / 0x400 * u 446fc1acf31SPaul Cercueil */ 447fc1acf31SPaul Cercueil regmap_write(ipu->map, JZ_REG_IPU_CSC_C0_COEF, 0x4a8); 448fc1acf31SPaul Cercueil regmap_write(ipu->map, JZ_REG_IPU_CSC_C1_COEF, 0x662); 449fc1acf31SPaul Cercueil regmap_write(ipu->map, JZ_REG_IPU_CSC_C2_COEF, 0x191); 450fc1acf31SPaul Cercueil regmap_write(ipu->map, JZ_REG_IPU_CSC_C3_COEF, 0x341); 451fc1acf31SPaul Cercueil regmap_write(ipu->map, JZ_REG_IPU_CSC_C4_COEF, 0x811); 452fc1acf31SPaul Cercueil } 453fc1acf31SPaul Cercueil 454fc1acf31SPaul Cercueil ctrl = 0; 455fc1acf31SPaul Cercueil 456fc1acf31SPaul Cercueil /* 457fc1acf31SPaul Cercueil * Must set ZOOM_SEL before programming bicubic LUTs. 458fc1acf31SPaul Cercueil * If the IPU supports bicubic, we enable it unconditionally, since it 459fc1acf31SPaul Cercueil * can do anything bilinear can and more. 460fc1acf31SPaul Cercueil */ 461fc1acf31SPaul Cercueil if (ipu->soc_info->has_bicubic) 462fc1acf31SPaul Cercueil ctrl |= JZ_IPU_CTRL_ZOOM_SEL; 463fc1acf31SPaul Cercueil 464fc1acf31SPaul Cercueil upscaling_w = ipu->num_w > ipu->denom_w; 465fc1acf31SPaul Cercueil if (upscaling_w) 466fc1acf31SPaul Cercueil ctrl |= JZ_IPU_CTRL_HSCALE; 467fc1acf31SPaul Cercueil 468fc1acf31SPaul Cercueil if (ipu->num_w != 1 || ipu->denom_w != 1) { 469fc1acf31SPaul Cercueil if (!ipu->soc_info->has_bicubic && !upscaling_w) 470fc1acf31SPaul Cercueil coef_index |= (ipu->denom_w - 1) << 16; 471fc1acf31SPaul Cercueil else 472fc1acf31SPaul Cercueil coef_index |= (ipu->num_w - 1) << 16; 473fc1acf31SPaul Cercueil ctrl |= JZ_IPU_CTRL_HRSZ_EN; 474fc1acf31SPaul Cercueil } 475fc1acf31SPaul Cercueil 476fc1acf31SPaul Cercueil upscaling_h = ipu->num_h > ipu->denom_h; 477fc1acf31SPaul Cercueil if (upscaling_h) 478fc1acf31SPaul Cercueil ctrl |= JZ_IPU_CTRL_VSCALE; 479fc1acf31SPaul Cercueil 480fc1acf31SPaul Cercueil if (ipu->num_h != 1 || ipu->denom_h != 1) { 481fc1acf31SPaul Cercueil if (!ipu->soc_info->has_bicubic && !upscaling_h) 482fc1acf31SPaul Cercueil coef_index |= ipu->denom_h - 1; 483fc1acf31SPaul Cercueil else 484fc1acf31SPaul Cercueil coef_index |= ipu->num_h - 1; 485fc1acf31SPaul Cercueil ctrl |= JZ_IPU_CTRL_VRSZ_EN; 486fc1acf31SPaul Cercueil } 487fc1acf31SPaul Cercueil 488fc1acf31SPaul Cercueil regmap_update_bits(ipu->map, JZ_REG_IPU_CTRL, JZ_IPU_CTRL_ZOOM_SEL | 489fc1acf31SPaul Cercueil JZ_IPU_CTRL_HRSZ_EN | JZ_IPU_CTRL_VRSZ_EN | 490fc1acf31SPaul Cercueil JZ_IPU_CTRL_HSCALE | JZ_IPU_CTRL_VSCALE, ctrl); 491fc1acf31SPaul Cercueil 492fc1acf31SPaul Cercueil /* Set the LUT index register */ 493fc1acf31SPaul Cercueil regmap_write(ipu->map, JZ_REG_IPU_RSZ_COEF_INDEX, coef_index); 494fc1acf31SPaul Cercueil 495fc1acf31SPaul Cercueil if (ipu->num_w != 1 || ipu->denom_w != 1) 496fc1acf31SPaul Cercueil ingenic_ipu_set_coefs(ipu, JZ_REG_IPU_HRSZ_COEF_LUT, 497fc1acf31SPaul Cercueil ipu->num_w, ipu->denom_w); 498fc1acf31SPaul Cercueil 499fc1acf31SPaul Cercueil if (ipu->num_h != 1 || ipu->denom_h != 1) 500fc1acf31SPaul Cercueil ingenic_ipu_set_coefs(ipu, JZ_REG_IPU_VRSZ_COEF_LUT, 501fc1acf31SPaul Cercueil ipu->num_h, ipu->denom_h); 502fc1acf31SPaul Cercueil 503fc1acf31SPaul Cercueil /* Clear STATUS register */ 504fc1acf31SPaul Cercueil regmap_write(ipu->map, JZ_REG_IPU_STATUS, 0); 505fc1acf31SPaul Cercueil 506fc1acf31SPaul Cercueil /* Start IPU */ 507fc1acf31SPaul Cercueil regmap_set_bits(ipu->map, JZ_REG_IPU_CTRL, 508fc1acf31SPaul Cercueil JZ_IPU_CTRL_RUN | JZ_IPU_CTRL_FM_IRQ_EN); 509fc1acf31SPaul Cercueil 510fc1acf31SPaul Cercueil dev_dbg(ipu->dev, "Scaling %ux%u to %ux%u (%u:%u horiz, %u:%u vert)\n", 511fc1acf31SPaul Cercueil state->src_w >> 16, state->src_h >> 16, 512fc1acf31SPaul Cercueil state->crtc_w, state->crtc_h, 513fc1acf31SPaul Cercueil ipu->num_w, ipu->denom_w, ipu->num_h, ipu->denom_h); 514fc1acf31SPaul Cercueil } 515fc1acf31SPaul Cercueil 516fc1acf31SPaul Cercueil static int ingenic_ipu_plane_atomic_check(struct drm_plane *plane, 5177c11b99aSMaxime Ripard struct drm_atomic_state *state) 518fc1acf31SPaul Cercueil { 5197c11b99aSMaxime Ripard struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, 5207c11b99aSMaxime Ripard plane); 521abec017cSPaul Cercueil unsigned int num_w, denom_w, num_h, denom_h, xres, yres, max_w, max_h; 522fc1acf31SPaul Cercueil struct ingenic_ipu *ipu = plane_to_ingenic_ipu(plane); 523ba5c1649SMaxime Ripard struct drm_crtc *crtc = new_plane_state->crtc ?: plane->state->crtc; 524fc1acf31SPaul Cercueil struct drm_crtc_state *crtc_state; 525fc1acf31SPaul Cercueil 526fc1acf31SPaul Cercueil if (!crtc) 527fc1acf31SPaul Cercueil return 0; 528fc1acf31SPaul Cercueil 529*dec92020SMaxime Ripard crtc_state = drm_atomic_get_existing_crtc_state(state, crtc); 530fc1acf31SPaul Cercueil if (WARN_ON(!crtc_state)) 531fc1acf31SPaul Cercueil return -EINVAL; 532fc1acf31SPaul Cercueil 533fc1acf31SPaul Cercueil /* Request a full modeset if we are enabling or disabling the IPU. */ 534ba5c1649SMaxime Ripard if (!plane->state->crtc ^ !new_plane_state->crtc) 535fc1acf31SPaul Cercueil crtc_state->mode_changed = true; 536fc1acf31SPaul Cercueil 537ba5c1649SMaxime Ripard if (!new_plane_state->crtc || 538fc1acf31SPaul Cercueil !crtc_state->mode.hdisplay || !crtc_state->mode.vdisplay) 53921271d41SPaul Cercueil return 0; 540fc1acf31SPaul Cercueil 541fc1acf31SPaul Cercueil /* Plane must be fully visible */ 542ba5c1649SMaxime Ripard if (new_plane_state->crtc_x < 0 || new_plane_state->crtc_y < 0 || 543ba5c1649SMaxime Ripard new_plane_state->crtc_x + new_plane_state->crtc_w > crtc_state->mode.hdisplay || 544ba5c1649SMaxime Ripard new_plane_state->crtc_y + new_plane_state->crtc_h > crtc_state->mode.vdisplay) 545fc1acf31SPaul Cercueil return -EINVAL; 546fc1acf31SPaul Cercueil 547fc1acf31SPaul Cercueil /* Minimum size is 4x4 */ 548ba5c1649SMaxime Ripard if ((new_plane_state->src_w >> 16) < 4 || (new_plane_state->src_h >> 16) < 4) 549fc1acf31SPaul Cercueil return -EINVAL; 550fc1acf31SPaul Cercueil 551fc1acf31SPaul Cercueil /* Input and output lines must have an even number of pixels. */ 552ba5c1649SMaxime Ripard if (((new_plane_state->src_w >> 16) & 1) || (new_plane_state->crtc_w & 1)) 553fc1acf31SPaul Cercueil return -EINVAL; 554fc1acf31SPaul Cercueil 555ba5c1649SMaxime Ripard if (!osd_changed(new_plane_state, plane->state)) 55621271d41SPaul Cercueil return 0; 557fc1acf31SPaul Cercueil 558fc1acf31SPaul Cercueil crtc_state->mode_changed = true; 559fc1acf31SPaul Cercueil 560ba5c1649SMaxime Ripard xres = new_plane_state->src_w >> 16; 561ba5c1649SMaxime Ripard yres = new_plane_state->src_h >> 16; 562fc1acf31SPaul Cercueil 563abec017cSPaul Cercueil /* 564abec017cSPaul Cercueil * Increase the scaled image's theorical width/height until we find a 565abec017cSPaul Cercueil * configuration that has valid scaling coefficients, up to 102% of the 566abec017cSPaul Cercueil * screen's resolution. This makes sure that we can scale from almost 567abec017cSPaul Cercueil * every resolution possible at the cost of a very small distorsion. 568abec017cSPaul Cercueil * The CRTC_W / CRTC_H are not modified. 569abec017cSPaul Cercueil */ 570abec017cSPaul Cercueil max_w = crtc_state->mode.hdisplay * 102 / 100; 571abec017cSPaul Cercueil max_h = crtc_state->mode.vdisplay * 102 / 100; 572abec017cSPaul Cercueil 573ba5c1649SMaxime Ripard for (denom_w = xres, num_w = new_plane_state->crtc_w; num_w <= max_w; num_w++) 574fc1acf31SPaul Cercueil if (!reduce_fraction(&num_w, &denom_w)) 575fc1acf31SPaul Cercueil break; 576abec017cSPaul Cercueil if (num_w > max_w) 577fc1acf31SPaul Cercueil return -EINVAL; 578fc1acf31SPaul Cercueil 579ba5c1649SMaxime Ripard for (denom_h = yres, num_h = new_plane_state->crtc_h; num_h <= max_h; num_h++) 580fc1acf31SPaul Cercueil if (!reduce_fraction(&num_h, &denom_h)) 581fc1acf31SPaul Cercueil break; 582abec017cSPaul Cercueil if (num_h > max_h) 583fc1acf31SPaul Cercueil return -EINVAL; 584fc1acf31SPaul Cercueil 585fc1acf31SPaul Cercueil ipu->num_w = num_w; 586fc1acf31SPaul Cercueil ipu->num_h = num_h; 587fc1acf31SPaul Cercueil ipu->denom_w = denom_w; 588fc1acf31SPaul Cercueil ipu->denom_h = denom_h; 589fc1acf31SPaul Cercueil 590fc1acf31SPaul Cercueil return 0; 591fc1acf31SPaul Cercueil } 592fc1acf31SPaul Cercueil 593fc1acf31SPaul Cercueil static void ingenic_ipu_plane_atomic_disable(struct drm_plane *plane, 594fc1acf31SPaul Cercueil struct drm_plane_state *old_state) 595fc1acf31SPaul Cercueil { 596fc1acf31SPaul Cercueil struct ingenic_ipu *ipu = plane_to_ingenic_ipu(plane); 597fc1acf31SPaul Cercueil 598fc1acf31SPaul Cercueil regmap_set_bits(ipu->map, JZ_REG_IPU_CTRL, JZ_IPU_CTRL_STOP); 599fc1acf31SPaul Cercueil regmap_clear_bits(ipu->map, JZ_REG_IPU_CTRL, JZ_IPU_CTRL_CHIP_EN); 600fc1acf31SPaul Cercueil 601fc1acf31SPaul Cercueil ingenic_drm_plane_disable(ipu->master, plane); 60238ee474fSPaul Cercueil 60338ee474fSPaul Cercueil if (ipu->clk_enabled) { 60438ee474fSPaul Cercueil clk_disable(ipu->clk); 60538ee474fSPaul Cercueil ipu->clk_enabled = false; 60638ee474fSPaul Cercueil } 607fc1acf31SPaul Cercueil } 608fc1acf31SPaul Cercueil 609fc1acf31SPaul Cercueil static const struct drm_plane_helper_funcs ingenic_ipu_plane_helper_funcs = { 610fc1acf31SPaul Cercueil .atomic_update = ingenic_ipu_plane_atomic_update, 611fc1acf31SPaul Cercueil .atomic_check = ingenic_ipu_plane_atomic_check, 612fc1acf31SPaul Cercueil .atomic_disable = ingenic_ipu_plane_atomic_disable, 613820c1707SThomas Zimmermann .prepare_fb = drm_gem_plane_helper_prepare_fb, 614fc1acf31SPaul Cercueil }; 615fc1acf31SPaul Cercueil 616fc1acf31SPaul Cercueil static int 617fc1acf31SPaul Cercueil ingenic_ipu_plane_atomic_get_property(struct drm_plane *plane, 618fc1acf31SPaul Cercueil const struct drm_plane_state *state, 619fc1acf31SPaul Cercueil struct drm_property *property, u64 *val) 620fc1acf31SPaul Cercueil { 621fc1acf31SPaul Cercueil struct ingenic_ipu *ipu = plane_to_ingenic_ipu(plane); 622fc1acf31SPaul Cercueil 623fc1acf31SPaul Cercueil if (property != ipu->sharpness_prop) 624fc1acf31SPaul Cercueil return -EINVAL; 625fc1acf31SPaul Cercueil 626fc1acf31SPaul Cercueil *val = ipu->sharpness; 627fc1acf31SPaul Cercueil 628fc1acf31SPaul Cercueil return 0; 629fc1acf31SPaul Cercueil } 630fc1acf31SPaul Cercueil 631fc1acf31SPaul Cercueil static int 632fc1acf31SPaul Cercueil ingenic_ipu_plane_atomic_set_property(struct drm_plane *plane, 633fc1acf31SPaul Cercueil struct drm_plane_state *state, 634fc1acf31SPaul Cercueil struct drm_property *property, u64 val) 635fc1acf31SPaul Cercueil { 636fc1acf31SPaul Cercueil struct ingenic_ipu *ipu = plane_to_ingenic_ipu(plane); 637fc1acf31SPaul Cercueil struct drm_crtc_state *crtc_state; 638fc1acf31SPaul Cercueil 639fc1acf31SPaul Cercueil if (property != ipu->sharpness_prop) 640fc1acf31SPaul Cercueil return -EINVAL; 641fc1acf31SPaul Cercueil 642fc1acf31SPaul Cercueil ipu->sharpness = val; 643fc1acf31SPaul Cercueil 644fc1acf31SPaul Cercueil if (state->crtc) { 645fc1acf31SPaul Cercueil crtc_state = drm_atomic_get_existing_crtc_state(state->state, state->crtc); 646fc1acf31SPaul Cercueil if (WARN_ON(!crtc_state)) 647fc1acf31SPaul Cercueil return -EINVAL; 648fc1acf31SPaul Cercueil 649fc1acf31SPaul Cercueil crtc_state->mode_changed = true; 650fc1acf31SPaul Cercueil } 651fc1acf31SPaul Cercueil 652fc1acf31SPaul Cercueil return 0; 653fc1acf31SPaul Cercueil } 654fc1acf31SPaul Cercueil 655fc1acf31SPaul Cercueil static const struct drm_plane_funcs ingenic_ipu_plane_funcs = { 656fc1acf31SPaul Cercueil .update_plane = drm_atomic_helper_update_plane, 657fc1acf31SPaul Cercueil .disable_plane = drm_atomic_helper_disable_plane, 658fc1acf31SPaul Cercueil .reset = drm_atomic_helper_plane_reset, 659fc1acf31SPaul Cercueil .destroy = drm_plane_cleanup, 660fc1acf31SPaul Cercueil 661fc1acf31SPaul Cercueil .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, 662fc1acf31SPaul Cercueil .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, 663fc1acf31SPaul Cercueil 664fc1acf31SPaul Cercueil .atomic_get_property = ingenic_ipu_plane_atomic_get_property, 665fc1acf31SPaul Cercueil .atomic_set_property = ingenic_ipu_plane_atomic_set_property, 666fc1acf31SPaul Cercueil }; 667fc1acf31SPaul Cercueil 668fc1acf31SPaul Cercueil static irqreturn_t ingenic_ipu_irq_handler(int irq, void *arg) 669fc1acf31SPaul Cercueil { 670fc1acf31SPaul Cercueil struct ingenic_ipu *ipu = arg; 671fc1acf31SPaul Cercueil struct drm_crtc *crtc = drm_crtc_from_index(ipu->drm, 0); 672fc1acf31SPaul Cercueil unsigned int dummy; 673fc1acf31SPaul Cercueil 674fc1acf31SPaul Cercueil /* dummy read allows CPU to reconfigure IPU */ 6753debcdf0SPaul Cercueil if (ipu->soc_info->manual_restart) 676fc1acf31SPaul Cercueil regmap_read(ipu->map, JZ_REG_IPU_STATUS, &dummy); 677fc1acf31SPaul Cercueil 678fc1acf31SPaul Cercueil /* ACK interrupt */ 679fc1acf31SPaul Cercueil regmap_write(ipu->map, JZ_REG_IPU_STATUS, 0); 680fc1acf31SPaul Cercueil 681fc1acf31SPaul Cercueil /* Set previously cached addresses */ 682fc1acf31SPaul Cercueil regmap_write(ipu->map, JZ_REG_IPU_Y_ADDR, ipu->addr_y); 683fc1acf31SPaul Cercueil regmap_write(ipu->map, JZ_REG_IPU_U_ADDR, ipu->addr_u); 684fc1acf31SPaul Cercueil regmap_write(ipu->map, JZ_REG_IPU_V_ADDR, ipu->addr_v); 685fc1acf31SPaul Cercueil 686fc1acf31SPaul Cercueil /* Run IPU for the new frame */ 6873debcdf0SPaul Cercueil if (ipu->soc_info->manual_restart) 688fc1acf31SPaul Cercueil regmap_set_bits(ipu->map, JZ_REG_IPU_CTRL, JZ_IPU_CTRL_RUN); 689fc1acf31SPaul Cercueil 690fc1acf31SPaul Cercueil drm_crtc_handle_vblank(crtc); 691fc1acf31SPaul Cercueil 692fc1acf31SPaul Cercueil return IRQ_HANDLED; 693fc1acf31SPaul Cercueil } 694fc1acf31SPaul Cercueil 695fc1acf31SPaul Cercueil static const struct regmap_config ingenic_ipu_regmap_config = { 696fc1acf31SPaul Cercueil .reg_bits = 32, 697fc1acf31SPaul Cercueil .val_bits = 32, 698fc1acf31SPaul Cercueil .reg_stride = 4, 699fc1acf31SPaul Cercueil 700fc1acf31SPaul Cercueil .max_register = JZ_REG_IPU_OUT_PHY_T_ADDR, 701fc1acf31SPaul Cercueil }; 702fc1acf31SPaul Cercueil 703fc1acf31SPaul Cercueil static int ingenic_ipu_bind(struct device *dev, struct device *master, void *d) 704fc1acf31SPaul Cercueil { 705fc1acf31SPaul Cercueil struct platform_device *pdev = to_platform_device(dev); 706fc1acf31SPaul Cercueil const struct soc_info *soc_info; 707fc1acf31SPaul Cercueil struct drm_device *drm = d; 708fc1acf31SPaul Cercueil struct drm_plane *plane; 709fc1acf31SPaul Cercueil struct ingenic_ipu *ipu; 710fc1acf31SPaul Cercueil void __iomem *base; 711fc1acf31SPaul Cercueil unsigned int sharpness_max; 712fc1acf31SPaul Cercueil int err, irq; 713fc1acf31SPaul Cercueil 714fc1acf31SPaul Cercueil ipu = devm_kzalloc(dev, sizeof(*ipu), GFP_KERNEL); 715fc1acf31SPaul Cercueil if (!ipu) 716fc1acf31SPaul Cercueil return -ENOMEM; 717fc1acf31SPaul Cercueil 718fc1acf31SPaul Cercueil soc_info = of_device_get_match_data(dev); 719fc1acf31SPaul Cercueil if (!soc_info) { 720fc1acf31SPaul Cercueil dev_err(dev, "Missing platform data\n"); 721fc1acf31SPaul Cercueil return -EINVAL; 722fc1acf31SPaul Cercueil } 723fc1acf31SPaul Cercueil 724fc1acf31SPaul Cercueil ipu->dev = dev; 725fc1acf31SPaul Cercueil ipu->drm = drm; 726fc1acf31SPaul Cercueil ipu->master = master; 727fc1acf31SPaul Cercueil ipu->soc_info = soc_info; 728fc1acf31SPaul Cercueil 729fc1acf31SPaul Cercueil base = devm_platform_ioremap_resource(pdev, 0); 730fc1acf31SPaul Cercueil if (IS_ERR(base)) { 731fc1acf31SPaul Cercueil dev_err(dev, "Failed to get memory resource\n"); 732fc1acf31SPaul Cercueil return PTR_ERR(base); 733fc1acf31SPaul Cercueil } 734fc1acf31SPaul Cercueil 735fc1acf31SPaul Cercueil ipu->map = devm_regmap_init_mmio(dev, base, &ingenic_ipu_regmap_config); 736fc1acf31SPaul Cercueil if (IS_ERR(ipu->map)) { 737fc1acf31SPaul Cercueil dev_err(dev, "Failed to create regmap\n"); 738fc1acf31SPaul Cercueil return PTR_ERR(ipu->map); 739fc1acf31SPaul Cercueil } 740fc1acf31SPaul Cercueil 741fc1acf31SPaul Cercueil irq = platform_get_irq(pdev, 0); 742fc1acf31SPaul Cercueil if (irq < 0) 743fc1acf31SPaul Cercueil return irq; 744fc1acf31SPaul Cercueil 745fc1acf31SPaul Cercueil ipu->clk = devm_clk_get(dev, "ipu"); 746fc1acf31SPaul Cercueil if (IS_ERR(ipu->clk)) { 747fc1acf31SPaul Cercueil dev_err(dev, "Failed to get pixel clock\n"); 748fc1acf31SPaul Cercueil return PTR_ERR(ipu->clk); 749fc1acf31SPaul Cercueil } 750fc1acf31SPaul Cercueil 751fc1acf31SPaul Cercueil err = devm_request_irq(dev, irq, ingenic_ipu_irq_handler, 0, 752fc1acf31SPaul Cercueil dev_name(dev), ipu); 753fc1acf31SPaul Cercueil if (err) { 754fc1acf31SPaul Cercueil dev_err(dev, "Unable to request IRQ\n"); 755fc1acf31SPaul Cercueil return err; 756fc1acf31SPaul Cercueil } 757fc1acf31SPaul Cercueil 758fc1acf31SPaul Cercueil plane = &ipu->plane; 759fc1acf31SPaul Cercueil dev_set_drvdata(dev, plane); 760fc1acf31SPaul Cercueil 761fc1acf31SPaul Cercueil drm_plane_helper_add(plane, &ingenic_ipu_plane_helper_funcs); 762fc1acf31SPaul Cercueil 763fc1acf31SPaul Cercueil err = drm_universal_plane_init(drm, plane, 1, &ingenic_ipu_plane_funcs, 764fc1acf31SPaul Cercueil soc_info->formats, soc_info->num_formats, 765fc1acf31SPaul Cercueil NULL, DRM_PLANE_TYPE_PRIMARY, NULL); 766fc1acf31SPaul Cercueil if (err) { 767fc1acf31SPaul Cercueil dev_err(dev, "Failed to init plane: %i\n", err); 768fc1acf31SPaul Cercueil return err; 769fc1acf31SPaul Cercueil } 770fc1acf31SPaul Cercueil 771fc1acf31SPaul Cercueil /* 772fc1acf31SPaul Cercueil * Sharpness settings range is [0,32] 773fc1acf31SPaul Cercueil * 0 : nearest-neighbor 774fc1acf31SPaul Cercueil * 1 : bilinear 775fc1acf31SPaul Cercueil * 2 .. 32 : bicubic (translated to sharpness factor -0.25 .. -4.0) 776fc1acf31SPaul Cercueil */ 777fc1acf31SPaul Cercueil sharpness_max = soc_info->has_bicubic ? 32 : 1; 778fc1acf31SPaul Cercueil ipu->sharpness_prop = drm_property_create_range(drm, 0, "sharpness", 779fc1acf31SPaul Cercueil 0, sharpness_max); 780fc1acf31SPaul Cercueil if (!ipu->sharpness_prop) { 781fc1acf31SPaul Cercueil dev_err(dev, "Unable to create sharpness property\n"); 782fc1acf31SPaul Cercueil return -ENOMEM; 783fc1acf31SPaul Cercueil } 784fc1acf31SPaul Cercueil 785fc1acf31SPaul Cercueil /* Default sharpness factor: -0.125 * 8 = -1.0 */ 786fc1acf31SPaul Cercueil ipu->sharpness = soc_info->has_bicubic ? 8 : 1; 787fc1acf31SPaul Cercueil drm_object_attach_property(&plane->base, ipu->sharpness_prop, 788fc1acf31SPaul Cercueil ipu->sharpness); 789fc1acf31SPaul Cercueil 79038ee474fSPaul Cercueil err = clk_prepare(ipu->clk); 791fc1acf31SPaul Cercueil if (err) { 79238ee474fSPaul Cercueil dev_err(dev, "Unable to prepare clock\n"); 793fc1acf31SPaul Cercueil return err; 794fc1acf31SPaul Cercueil } 795fc1acf31SPaul Cercueil 796fc1acf31SPaul Cercueil return 0; 797fc1acf31SPaul Cercueil } 798fc1acf31SPaul Cercueil 799fc1acf31SPaul Cercueil static void ingenic_ipu_unbind(struct device *dev, 800fc1acf31SPaul Cercueil struct device *master, void *d) 801fc1acf31SPaul Cercueil { 802fc1acf31SPaul Cercueil struct ingenic_ipu *ipu = dev_get_drvdata(dev); 803fc1acf31SPaul Cercueil 80438ee474fSPaul Cercueil clk_unprepare(ipu->clk); 805fc1acf31SPaul Cercueil } 806fc1acf31SPaul Cercueil 807fc1acf31SPaul Cercueil static const struct component_ops ingenic_ipu_ops = { 808fc1acf31SPaul Cercueil .bind = ingenic_ipu_bind, 809fc1acf31SPaul Cercueil .unbind = ingenic_ipu_unbind, 810fc1acf31SPaul Cercueil }; 811fc1acf31SPaul Cercueil 812fc1acf31SPaul Cercueil static int ingenic_ipu_probe(struct platform_device *pdev) 813fc1acf31SPaul Cercueil { 814fc1acf31SPaul Cercueil return component_add(&pdev->dev, &ingenic_ipu_ops); 815fc1acf31SPaul Cercueil } 816fc1acf31SPaul Cercueil 817fc1acf31SPaul Cercueil static int ingenic_ipu_remove(struct platform_device *pdev) 818fc1acf31SPaul Cercueil { 819fc1acf31SPaul Cercueil component_del(&pdev->dev, &ingenic_ipu_ops); 820fc1acf31SPaul Cercueil return 0; 821fc1acf31SPaul Cercueil } 822fc1acf31SPaul Cercueil 823fc1acf31SPaul Cercueil static const u32 jz4725b_ipu_formats[] = { 824c0fd208eSPaul Cercueil /* 825c0fd208eSPaul Cercueil * While officially supported, packed YUV 4:2:2 formats can cause 826c0fd208eSPaul Cercueil * random hardware crashes on JZ4725B under certain circumstances. 827c0fd208eSPaul Cercueil * It seems to happen with some specific resize ratios. 828c0fd208eSPaul Cercueil * Until a proper workaround or fix is found, disable these formats. 829fc1acf31SPaul Cercueil DRM_FORMAT_YUYV, 830fc1acf31SPaul Cercueil DRM_FORMAT_YVYU, 831fc1acf31SPaul Cercueil DRM_FORMAT_UYVY, 832fc1acf31SPaul Cercueil DRM_FORMAT_VYUY, 833c0fd208eSPaul Cercueil */ 834fc1acf31SPaul Cercueil DRM_FORMAT_YUV411, 835fc1acf31SPaul Cercueil DRM_FORMAT_YUV420, 836fc1acf31SPaul Cercueil DRM_FORMAT_YUV422, 837fc1acf31SPaul Cercueil DRM_FORMAT_YUV444, 838fc1acf31SPaul Cercueil }; 839fc1acf31SPaul Cercueil 840fc1acf31SPaul Cercueil static const struct soc_info jz4725b_soc_info = { 841fc1acf31SPaul Cercueil .formats = jz4725b_ipu_formats, 842fc1acf31SPaul Cercueil .num_formats = ARRAY_SIZE(jz4725b_ipu_formats), 843fc1acf31SPaul Cercueil .has_bicubic = false, 8443debcdf0SPaul Cercueil .manual_restart = true, 845fc1acf31SPaul Cercueil .set_coefs = jz4725b_set_coefs, 846fc1acf31SPaul Cercueil }; 847fc1acf31SPaul Cercueil 848fc1acf31SPaul Cercueil static const u32 jz4760_ipu_formats[] = { 849fc1acf31SPaul Cercueil DRM_FORMAT_XRGB1555, 850fc1acf31SPaul Cercueil DRM_FORMAT_XBGR1555, 851fc1acf31SPaul Cercueil DRM_FORMAT_RGB565, 852fc1acf31SPaul Cercueil DRM_FORMAT_BGR565, 853fc1acf31SPaul Cercueil DRM_FORMAT_XRGB8888, 854fc1acf31SPaul Cercueil DRM_FORMAT_XBGR8888, 855fc1acf31SPaul Cercueil DRM_FORMAT_YUYV, 856fc1acf31SPaul Cercueil DRM_FORMAT_YVYU, 857fc1acf31SPaul Cercueil DRM_FORMAT_UYVY, 858fc1acf31SPaul Cercueil DRM_FORMAT_VYUY, 859fc1acf31SPaul Cercueil DRM_FORMAT_YUV411, 860fc1acf31SPaul Cercueil DRM_FORMAT_YUV420, 861fc1acf31SPaul Cercueil DRM_FORMAT_YUV422, 862fc1acf31SPaul Cercueil DRM_FORMAT_YUV444, 863fc1acf31SPaul Cercueil DRM_FORMAT_XYUV8888, 864fc1acf31SPaul Cercueil }; 865fc1acf31SPaul Cercueil 866fc1acf31SPaul Cercueil static const struct soc_info jz4760_soc_info = { 867fc1acf31SPaul Cercueil .formats = jz4760_ipu_formats, 868fc1acf31SPaul Cercueil .num_formats = ARRAY_SIZE(jz4760_ipu_formats), 869fc1acf31SPaul Cercueil .has_bicubic = true, 8703debcdf0SPaul Cercueil .manual_restart = false, 871fc1acf31SPaul Cercueil .set_coefs = jz4760_set_coefs, 872fc1acf31SPaul Cercueil }; 873fc1acf31SPaul Cercueil 874fc1acf31SPaul Cercueil static const struct of_device_id ingenic_ipu_of_match[] = { 875fc1acf31SPaul Cercueil { .compatible = "ingenic,jz4725b-ipu", .data = &jz4725b_soc_info }, 876fc1acf31SPaul Cercueil { .compatible = "ingenic,jz4760-ipu", .data = &jz4760_soc_info }, 877fc1acf31SPaul Cercueil { /* sentinel */ }, 878fc1acf31SPaul Cercueil }; 879fc1acf31SPaul Cercueil MODULE_DEVICE_TABLE(of, ingenic_ipu_of_match); 880fc1acf31SPaul Cercueil 881fc1acf31SPaul Cercueil static struct platform_driver ingenic_ipu_driver = { 882fc1acf31SPaul Cercueil .driver = { 883fc1acf31SPaul Cercueil .name = "ingenic-ipu", 884fc1acf31SPaul Cercueil .of_match_table = ingenic_ipu_of_match, 885fc1acf31SPaul Cercueil }, 886fc1acf31SPaul Cercueil .probe = ingenic_ipu_probe, 887fc1acf31SPaul Cercueil .remove = ingenic_ipu_remove, 888fc1acf31SPaul Cercueil }; 889fc1acf31SPaul Cercueil 890fc1acf31SPaul Cercueil struct platform_driver *ingenic_ipu_driver_ptr = &ingenic_ipu_driver; 891