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