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