diff options
Diffstat (limited to 'drivers/media/platform/sti/bdisp/bdisp-v4l2.c')
| -rw-r--r-- | drivers/media/platform/sti/bdisp/bdisp-v4l2.c | 1416 |
1 files changed, 1416 insertions, 0 deletions
diff --git a/drivers/media/platform/sti/bdisp/bdisp-v4l2.c b/drivers/media/platform/sti/bdisp/bdisp-v4l2.c new file mode 100644 index 000000000000..9e782ebe18da --- /dev/null +++ b/drivers/media/platform/sti/bdisp/bdisp-v4l2.c | |||
| @@ -0,0 +1,1416 @@ | |||
| 1 | /* | ||
| 2 | * Copyright (C) STMicroelectronics SA 2014 | ||
| 3 | * Authors: Fabien Dessenne <fabien.dessenne@st.com> for STMicroelectronics. | ||
| 4 | * License terms: GNU General Public License (GPL), version 2 | ||
| 5 | */ | ||
| 6 | |||
| 7 | #include <linux/errno.h> | ||
| 8 | #include <linux/interrupt.h> | ||
| 9 | #include <linux/kernel.h> | ||
| 10 | #include <linux/module.h> | ||
| 11 | #include <linux/of.h> | ||
| 12 | #include <linux/pm_runtime.h> | ||
| 13 | #include <linux/slab.h> | ||
| 14 | |||
| 15 | #include <media/v4l2-event.h> | ||
| 16 | #include <media/v4l2-ioctl.h> | ||
| 17 | |||
| 18 | #include "bdisp.h" | ||
| 19 | |||
| 20 | #define BDISP_MAX_CTRL_NUM 10 | ||
| 21 | |||
| 22 | #define BDISP_WORK_TIMEOUT ((100 * HZ) / 1000) | ||
| 23 | |||
| 24 | /* User configuration change */ | ||
| 25 | #define BDISP_PARAMS BIT(0) /* Config updated */ | ||
| 26 | #define BDISP_SRC_FMT BIT(1) /* Source set */ | ||
| 27 | #define BDISP_DST_FMT BIT(2) /* Destination set */ | ||
| 28 | #define BDISP_CTX_STOP_REQ BIT(3) /* Stop request */ | ||
| 29 | #define BDISP_CTX_ABORT BIT(4) /* Abort while device run */ | ||
| 30 | |||
| 31 | #define BDISP_MIN_W 1 | ||
| 32 | #define BDISP_MAX_W 8191 | ||
| 33 | #define BDISP_MIN_H 1 | ||
| 34 | #define BDISP_MAX_H 8191 | ||
| 35 | |||
| 36 | #define fh_to_ctx(__fh) container_of(__fh, struct bdisp_ctx, fh) | ||
| 37 | |||
| 38 | enum bdisp_dev_flags { | ||
| 39 | ST_M2M_OPEN, /* Driver opened */ | ||
| 40 | ST_M2M_RUNNING, /* HW device running */ | ||
| 41 | ST_M2M_SUSPENDED, /* Driver suspended */ | ||
| 42 | ST_M2M_SUSPENDING, /* Driver being suspended */ | ||
| 43 | }; | ||
| 44 | |||
| 45 | static const struct bdisp_fmt bdisp_formats[] = { | ||
| 46 | /* ARGB888. [31:0] A:R:G:B 8:8:8:8 little endian */ | ||
| 47 | { | ||
| 48 | .pixelformat = V4L2_PIX_FMT_ABGR32, /* is actually ARGB */ | ||
| 49 | .nb_planes = 1, | ||
| 50 | .bpp = 32, | ||
| 51 | .bpp_plane0 = 32, | ||
| 52 | .w_align = 1, | ||
| 53 | .h_align = 1 | ||
| 54 | }, | ||
| 55 | /* XRGB888. [31:0] x:R:G:B 8:8:8:8 little endian */ | ||
| 56 | { | ||
| 57 | .pixelformat = V4L2_PIX_FMT_XBGR32, /* is actually xRGB */ | ||
| 58 | .nb_planes = 1, | ||
| 59 | .bpp = 32, | ||
| 60 | .bpp_plane0 = 32, | ||
| 61 | .w_align = 1, | ||
| 62 | .h_align = 1 | ||
| 63 | }, | ||
| 64 | /* RGB565. [15:0] R:G:B 5:6:5 little endian */ | ||
| 65 | { | ||
| 66 | .pixelformat = V4L2_PIX_FMT_RGB565, | ||
| 67 | .nb_planes = 1, | ||
| 68 | .bpp = 16, | ||
| 69 | .bpp_plane0 = 16, | ||
| 70 | .w_align = 1, | ||
| 71 | .h_align = 1 | ||
| 72 | }, | ||
| 73 | /* NV12. YUV420SP - 1 plane for Y + 1 plane for (CbCr) */ | ||
| 74 | { | ||
| 75 | .pixelformat = V4L2_PIX_FMT_NV12, | ||
| 76 | .nb_planes = 2, | ||
| 77 | .bpp = 12, | ||
| 78 | .bpp_plane0 = 8, | ||
| 79 | .w_align = 2, | ||
| 80 | .h_align = 2 | ||
| 81 | }, | ||
| 82 | /* RGB888. [23:0] B:G:R 8:8:8 little endian */ | ||
| 83 | { | ||
| 84 | .pixelformat = V4L2_PIX_FMT_RGB24, | ||
| 85 | .nb_planes = 1, | ||
| 86 | .bpp = 24, | ||
| 87 | .bpp_plane0 = 24, | ||
| 88 | .w_align = 1, | ||
| 89 | .h_align = 1 | ||
| 90 | }, | ||
| 91 | /* YU12. YUV420P - 1 plane for Y + 1 plane for Cb + 1 plane for Cr | ||
| 92 | * To keep as the LAST element of this table (no support on capture) | ||
| 93 | */ | ||
| 94 | { | ||
| 95 | .pixelformat = V4L2_PIX_FMT_YUV420, | ||
| 96 | .nb_planes = 3, | ||
| 97 | .bpp = 12, | ||
| 98 | .bpp_plane0 = 8, | ||
| 99 | .w_align = 2, | ||
| 100 | .h_align = 2 | ||
| 101 | } | ||
| 102 | }; | ||
| 103 | |||
| 104 | /* Default format : HD ARGB32*/ | ||
| 105 | #define BDISP_DEF_WIDTH 1920 | ||
| 106 | #define BDISP_DEF_HEIGHT 1080 | ||
| 107 | |||
| 108 | static const struct bdisp_frame bdisp_dflt_fmt = { | ||
| 109 | .width = BDISP_DEF_WIDTH, | ||
| 110 | .height = BDISP_DEF_HEIGHT, | ||
| 111 | .fmt = &bdisp_formats[0], | ||
| 112 | .field = V4L2_FIELD_NONE, | ||
| 113 | .bytesperline = BDISP_DEF_WIDTH * 4, | ||
| 114 | .sizeimage = BDISP_DEF_WIDTH * BDISP_DEF_HEIGHT * 4, | ||
| 115 | .colorspace = V4L2_COLORSPACE_REC709, | ||
| 116 | .crop = {0, 0, BDISP_DEF_WIDTH, BDISP_DEF_HEIGHT}, | ||
| 117 | .paddr = {0, 0, 0, 0} | ||
| 118 | }; | ||
| 119 | |||
| 120 | static inline void bdisp_ctx_state_lock_set(u32 state, struct bdisp_ctx *ctx) | ||
| 121 | { | ||
| 122 | unsigned long flags; | ||
| 123 | |||
| 124 | spin_lock_irqsave(&ctx->bdisp_dev->slock, flags); | ||
| 125 | ctx->state |= state; | ||
| 126 | spin_unlock_irqrestore(&ctx->bdisp_dev->slock, flags); | ||
| 127 | } | ||
| 128 | |||
| 129 | static inline void bdisp_ctx_state_lock_clear(u32 state, struct bdisp_ctx *ctx) | ||
| 130 | { | ||
| 131 | unsigned long flags; | ||
| 132 | |||
| 133 | spin_lock_irqsave(&ctx->bdisp_dev->slock, flags); | ||
| 134 | ctx->state &= ~state; | ||
| 135 | spin_unlock_irqrestore(&ctx->bdisp_dev->slock, flags); | ||
| 136 | } | ||
| 137 | |||
| 138 | static inline bool bdisp_ctx_state_is_set(u32 mask, struct bdisp_ctx *ctx) | ||
| 139 | { | ||
| 140 | unsigned long flags; | ||
| 141 | bool ret; | ||
| 142 | |||
| 143 | spin_lock_irqsave(&ctx->bdisp_dev->slock, flags); | ||
| 144 | ret = (ctx->state & mask) == mask; | ||
| 145 | spin_unlock_irqrestore(&ctx->bdisp_dev->slock, flags); | ||
| 146 | |||
| 147 | return ret; | ||
| 148 | } | ||
| 149 | |||
| 150 | static const struct bdisp_fmt *bdisp_find_fmt(u32 pixelformat) | ||
| 151 | { | ||
| 152 | const struct bdisp_fmt *fmt; | ||
| 153 | unsigned int i; | ||
| 154 | |||
| 155 | for (i = 0; i < ARRAY_SIZE(bdisp_formats); i++) { | ||
| 156 | fmt = &bdisp_formats[i]; | ||
| 157 | if (fmt->pixelformat == pixelformat) | ||
| 158 | return fmt; | ||
| 159 | } | ||
| 160 | |||
| 161 | return NULL; | ||
| 162 | } | ||
| 163 | |||
| 164 | static struct bdisp_frame *ctx_get_frame(struct bdisp_ctx *ctx, | ||
| 165 | enum v4l2_buf_type type) | ||
| 166 | { | ||
| 167 | switch (type) { | ||
| 168 | case V4L2_BUF_TYPE_VIDEO_OUTPUT: | ||
| 169 | return &ctx->src; | ||
| 170 | case V4L2_BUF_TYPE_VIDEO_CAPTURE: | ||
| 171 | return &ctx->dst; | ||
| 172 | default: | ||
| 173 | dev_err(ctx->bdisp_dev->dev, | ||
| 174 | "Wrong buffer/video queue type (%d)\n", type); | ||
| 175 | break; | ||
| 176 | } | ||
| 177 | |||
| 178 | return ERR_PTR(-EINVAL); | ||
| 179 | } | ||
| 180 | |||
| 181 | static void bdisp_job_finish(struct bdisp_ctx *ctx, int vb_state) | ||
| 182 | { | ||
| 183 | struct vb2_buffer *src_vb, *dst_vb; | ||
| 184 | |||
| 185 | if (WARN(!ctx || !ctx->fh.m2m_ctx, "Null hardware context\n")) | ||
| 186 | return; | ||
| 187 | |||
| 188 | dev_dbg(ctx->bdisp_dev->dev, "%s\n", __func__); | ||
| 189 | |||
| 190 | src_vb = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); | ||
| 191 | dst_vb = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); | ||
| 192 | |||
| 193 | if (src_vb && dst_vb) { | ||
| 194 | dst_vb->v4l2_buf.timestamp = src_vb->v4l2_buf.timestamp; | ||
| 195 | dst_vb->v4l2_buf.timecode = src_vb->v4l2_buf.timecode; | ||
| 196 | dst_vb->v4l2_buf.flags &= ~V4L2_BUF_FLAG_TSTAMP_SRC_MASK; | ||
| 197 | dst_vb->v4l2_buf.flags |= src_vb->v4l2_buf.flags & | ||
| 198 | V4L2_BUF_FLAG_TSTAMP_SRC_MASK; | ||
| 199 | |||
| 200 | v4l2_m2m_buf_done(src_vb, vb_state); | ||
| 201 | v4l2_m2m_buf_done(dst_vb, vb_state); | ||
| 202 | |||
| 203 | v4l2_m2m_job_finish(ctx->bdisp_dev->m2m.m2m_dev, | ||
| 204 | ctx->fh.m2m_ctx); | ||
| 205 | } | ||
| 206 | } | ||
| 207 | |||
| 208 | static int bdisp_ctx_stop_req(struct bdisp_ctx *ctx) | ||
| 209 | { | ||
| 210 | struct bdisp_ctx *curr_ctx; | ||
| 211 | struct bdisp_dev *bdisp = ctx->bdisp_dev; | ||
| 212 | int ret; | ||
| 213 | |||
| 214 | dev_dbg(ctx->bdisp_dev->dev, "%s\n", __func__); | ||
| 215 | |||
| 216 | cancel_delayed_work(&bdisp->timeout_work); | ||
| 217 | |||
| 218 | curr_ctx = v4l2_m2m_get_curr_priv(bdisp->m2m.m2m_dev); | ||
| 219 | if (!test_bit(ST_M2M_RUNNING, &bdisp->state) || (curr_ctx != ctx)) | ||
| 220 | return 0; | ||
| 221 | |||
| 222 | bdisp_ctx_state_lock_set(BDISP_CTX_STOP_REQ, ctx); | ||
| 223 | |||
| 224 | ret = wait_event_timeout(bdisp->irq_queue, | ||
| 225 | !bdisp_ctx_state_is_set(BDISP_CTX_STOP_REQ, ctx), | ||
| 226 | BDISP_WORK_TIMEOUT); | ||
| 227 | |||
| 228 | if (!ret) { | ||
| 229 | dev_err(ctx->bdisp_dev->dev, "%s IRQ timeout\n", __func__); | ||
| 230 | return -ETIMEDOUT; | ||
| 231 | } | ||
| 232 | |||
| 233 | return 0; | ||
| 234 | } | ||
| 235 | |||
| 236 | static void __bdisp_job_abort(struct bdisp_ctx *ctx) | ||
| 237 | { | ||
| 238 | int ret; | ||
| 239 | |||
| 240 | ret = bdisp_ctx_stop_req(ctx); | ||
| 241 | if ((ret == -ETIMEDOUT) || (ctx->state & BDISP_CTX_ABORT)) { | ||
| 242 | bdisp_ctx_state_lock_clear(BDISP_CTX_STOP_REQ | BDISP_CTX_ABORT, | ||
| 243 | ctx); | ||
| 244 | bdisp_job_finish(ctx, VB2_BUF_STATE_ERROR); | ||
| 245 | } | ||
| 246 | } | ||
| 247 | |||
| 248 | static void bdisp_job_abort(void *priv) | ||
| 249 | { | ||
| 250 | __bdisp_job_abort((struct bdisp_ctx *)priv); | ||
| 251 | } | ||
| 252 | |||
| 253 | static int bdisp_get_addr(struct bdisp_ctx *ctx, struct vb2_buffer *vb, | ||
| 254 | struct bdisp_frame *frame, dma_addr_t *paddr) | ||
| 255 | { | ||
| 256 | if (!vb || !frame) | ||
| 257 | return -EINVAL; | ||
| 258 | |||
| 259 | paddr[0] = vb2_dma_contig_plane_dma_addr(vb, 0); | ||
| 260 | |||
| 261 | if (frame->fmt->nb_planes > 1) | ||
| 262 | /* UV (NV12) or U (420P) */ | ||
| 263 | paddr[1] = (dma_addr_t)(paddr[0] + | ||
| 264 | frame->bytesperline * frame->height); | ||
| 265 | |||
| 266 | if (frame->fmt->nb_planes > 2) | ||
| 267 | /* V (420P) */ | ||
| 268 | paddr[2] = (dma_addr_t)(paddr[1] + | ||
| 269 | (frame->bytesperline * frame->height) / 4); | ||
| 270 | |||
| 271 | if (frame->fmt->nb_planes > 3) | ||
| 272 | dev_dbg(ctx->bdisp_dev->dev, "ignoring some planes\n"); | ||
| 273 | |||
| 274 | dev_dbg(ctx->bdisp_dev->dev, | ||
| 275 | "%s plane[0]=%pad plane[1]=%pad plane[2]=%pad\n", | ||
| 276 | __func__, &paddr[0], &paddr[1], &paddr[2]); | ||
| 277 | |||
| 278 | return 0; | ||
| 279 | } | ||
| 280 | |||
| 281 | static int bdisp_get_bufs(struct bdisp_ctx *ctx) | ||
| 282 | { | ||
| 283 | struct bdisp_frame *src, *dst; | ||
| 284 | struct vb2_buffer *src_vb, *dst_vb; | ||
| 285 | int ret; | ||
| 286 | |||
| 287 | src = &ctx->src; | ||
| 288 | dst = &ctx->dst; | ||
| 289 | |||
| 290 | src_vb = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); | ||
| 291 | ret = bdisp_get_addr(ctx, src_vb, src, src->paddr); | ||
| 292 | if (ret) | ||
| 293 | return ret; | ||
| 294 | |||
| 295 | dst_vb = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); | ||
| 296 | ret = bdisp_get_addr(ctx, dst_vb, dst, dst->paddr); | ||
| 297 | if (ret) | ||
| 298 | return ret; | ||
| 299 | |||
| 300 | dst_vb->v4l2_buf.timestamp = src_vb->v4l2_buf.timestamp; | ||
| 301 | |||
| 302 | return 0; | ||
| 303 | } | ||
| 304 | |||
| 305 | static void bdisp_device_run(void *priv) | ||
| 306 | { | ||
| 307 | struct bdisp_ctx *ctx = priv; | ||
| 308 | struct bdisp_dev *bdisp; | ||
| 309 | unsigned long flags; | ||
| 310 | int err = 0; | ||
| 311 | |||
| 312 | if (WARN(!ctx, "Null hardware context\n")) | ||
| 313 | return; | ||
| 314 | |||
| 315 | bdisp = ctx->bdisp_dev; | ||
| 316 | dev_dbg(bdisp->dev, "%s\n", __func__); | ||
| 317 | spin_lock_irqsave(&bdisp->slock, flags); | ||
| 318 | |||
| 319 | if (bdisp->m2m.ctx != ctx) { | ||
| 320 | dev_dbg(bdisp->dev, "ctx updated: %p -> %p\n", | ||
| 321 | bdisp->m2m.ctx, ctx); | ||
| 322 | ctx->state |= BDISP_PARAMS; | ||
| 323 | bdisp->m2m.ctx = ctx; | ||
| 324 | } | ||
| 325 | |||
| 326 | if (ctx->state & BDISP_CTX_STOP_REQ) { | ||
| 327 | ctx->state &= ~BDISP_CTX_STOP_REQ; | ||
| 328 | ctx->state |= BDISP_CTX_ABORT; | ||
| 329 | wake_up(&bdisp->irq_queue); | ||
| 330 | goto out; | ||
| 331 | } | ||
| 332 | |||
| 333 | err = bdisp_get_bufs(ctx); | ||
| 334 | if (err) { | ||
| 335 | dev_err(bdisp->dev, "cannot get address\n"); | ||
| 336 | goto out; | ||
| 337 | } | ||
| 338 | |||
| 339 | bdisp_dbg_perf_begin(bdisp); | ||
| 340 | |||
| 341 | err = bdisp_hw_reset(bdisp); | ||
| 342 | if (err) { | ||
| 343 | dev_err(bdisp->dev, "could not get HW ready\n"); | ||
| 344 | goto out; | ||
| 345 | } | ||
| 346 | |||
| 347 | err = bdisp_hw_update(ctx); | ||
| 348 | if (err) { | ||
| 349 | dev_err(bdisp->dev, "could not send HW request\n"); | ||
| 350 | goto out; | ||
| 351 | } | ||
| 352 | |||
| 353 | queue_delayed_work(bdisp->work_queue, &bdisp->timeout_work, | ||
| 354 | BDISP_WORK_TIMEOUT); | ||
| 355 | set_bit(ST_M2M_RUNNING, &bdisp->state); | ||
| 356 | out: | ||
| 357 | ctx->state &= ~BDISP_PARAMS; | ||
| 358 | spin_unlock_irqrestore(&bdisp->slock, flags); | ||
| 359 | if (err) | ||
| 360 | bdisp_job_finish(ctx, VB2_BUF_STATE_ERROR); | ||
| 361 | } | ||
| 362 | |||
| 363 | static struct v4l2_m2m_ops bdisp_m2m_ops = { | ||
| 364 | .device_run = bdisp_device_run, | ||
| 365 | .job_abort = bdisp_job_abort, | ||
| 366 | }; | ||
| 367 | |||
| 368 | static int __bdisp_s_ctrl(struct bdisp_ctx *ctx, struct v4l2_ctrl *ctrl) | ||
| 369 | { | ||
| 370 | if (ctrl->flags & V4L2_CTRL_FLAG_INACTIVE) | ||
| 371 | return 0; | ||
| 372 | |||
| 373 | switch (ctrl->id) { | ||
| 374 | case V4L2_CID_HFLIP: | ||
| 375 | ctx->hflip = ctrl->val; | ||
| 376 | break; | ||
| 377 | case V4L2_CID_VFLIP: | ||
| 378 | ctx->vflip = ctrl->val; | ||
| 379 | break; | ||
| 380 | default: | ||
| 381 | dev_err(ctx->bdisp_dev->dev, "unknown control %d\n", ctrl->id); | ||
| 382 | return -EINVAL; | ||
| 383 | } | ||
| 384 | |||
| 385 | ctx->state |= BDISP_PARAMS; | ||
| 386 | |||
| 387 | return 0; | ||
| 388 | } | ||
| 389 | |||
| 390 | static int bdisp_s_ctrl(struct v4l2_ctrl *ctrl) | ||
| 391 | { | ||
| 392 | struct bdisp_ctx *ctx = container_of(ctrl->handler, struct bdisp_ctx, | ||
| 393 | ctrl_handler); | ||
| 394 | unsigned long flags; | ||
| 395 | int ret; | ||
| 396 | |||
| 397 | spin_lock_irqsave(&ctx->bdisp_dev->slock, flags); | ||
| 398 | ret = __bdisp_s_ctrl(ctx, ctrl); | ||
| 399 | spin_unlock_irqrestore(&ctx->bdisp_dev->slock, flags); | ||
| 400 | |||
| 401 | return ret; | ||
| 402 | } | ||
| 403 | |||
| 404 | static const struct v4l2_ctrl_ops bdisp_c_ops = { | ||
| 405 | .s_ctrl = bdisp_s_ctrl, | ||
| 406 | }; | ||
| 407 | |||
| 408 | static int bdisp_ctrls_create(struct bdisp_ctx *ctx) | ||
| 409 | { | ||
| 410 | if (ctx->ctrls_rdy) | ||
| 411 | return 0; | ||
| 412 | |||
| 413 | v4l2_ctrl_handler_init(&ctx->ctrl_handler, BDISP_MAX_CTRL_NUM); | ||
| 414 | |||
| 415 | ctx->bdisp_ctrls.hflip = v4l2_ctrl_new_std(&ctx->ctrl_handler, | ||
| 416 | &bdisp_c_ops, V4L2_CID_HFLIP, 0, 1, 1, 0); | ||
| 417 | ctx->bdisp_ctrls.vflip = v4l2_ctrl_new_std(&ctx->ctrl_handler, | ||
| 418 | &bdisp_c_ops, V4L2_CID_VFLIP, 0, 1, 1, 0); | ||
| 419 | |||
| 420 | if (ctx->ctrl_handler.error) { | ||
| 421 | int err = ctx->ctrl_handler.error; | ||
| 422 | |||
| 423 | v4l2_ctrl_handler_free(&ctx->ctrl_handler); | ||
| 424 | return err; | ||
| 425 | } | ||
| 426 | |||
| 427 | ctx->ctrls_rdy = true; | ||
| 428 | |||
| 429 | return 0; | ||
| 430 | } | ||
| 431 | |||
| 432 | static void bdisp_ctrls_delete(struct bdisp_ctx *ctx) | ||
| 433 | { | ||
| 434 | if (ctx->ctrls_rdy) { | ||
| 435 | v4l2_ctrl_handler_free(&ctx->ctrl_handler); | ||
| 436 | ctx->ctrls_rdy = false; | ||
| 437 | } | ||
| 438 | } | ||
| 439 | |||
| 440 | static int bdisp_queue_setup(struct vb2_queue *vq, | ||
| 441 | const struct v4l2_format *fmt, | ||
| 442 | unsigned int *nb_buf, unsigned int *nb_planes, | ||
| 443 | unsigned int sizes[], void *allocators[]) | ||
| 444 | { | ||
| 445 | struct bdisp_ctx *ctx = vb2_get_drv_priv(vq); | ||
| 446 | struct bdisp_frame *frame = ctx_get_frame(ctx, vq->type); | ||
| 447 | |||
| 448 | if (IS_ERR(frame)) { | ||
| 449 | dev_err(ctx->bdisp_dev->dev, "Invalid frame (%p)\n", frame); | ||
| 450 | return PTR_ERR(frame); | ||
| 451 | } | ||
| 452 | |||
| 453 | if (!frame->fmt) { | ||
| 454 | dev_err(ctx->bdisp_dev->dev, "Invalid format\n"); | ||
| 455 | return -EINVAL; | ||
| 456 | } | ||
| 457 | |||
| 458 | if (fmt && fmt->fmt.pix.sizeimage < frame->sizeimage) | ||
| 459 | return -EINVAL; | ||
| 460 | |||
| 461 | *nb_planes = 1; | ||
| 462 | sizes[0] = fmt ? fmt->fmt.pix.sizeimage : frame->sizeimage; | ||
| 463 | allocators[0] = ctx->bdisp_dev->alloc_ctx; | ||
| 464 | |||
| 465 | return 0; | ||
| 466 | } | ||
| 467 | |||
| 468 | static int bdisp_buf_prepare(struct vb2_buffer *vb) | ||
| 469 | { | ||
| 470 | struct bdisp_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); | ||
| 471 | struct bdisp_frame *frame = ctx_get_frame(ctx, vb->vb2_queue->type); | ||
| 472 | |||
| 473 | if (IS_ERR(frame)) { | ||
| 474 | dev_err(ctx->bdisp_dev->dev, "Invalid frame (%p)\n", frame); | ||
| 475 | return PTR_ERR(frame); | ||
| 476 | } | ||
| 477 | |||
| 478 | if (vb->vb2_queue->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) | ||
| 479 | vb2_set_plane_payload(vb, 0, frame->sizeimage); | ||
| 480 | |||
| 481 | return 0; | ||
| 482 | } | ||
| 483 | |||
| 484 | static void bdisp_buf_queue(struct vb2_buffer *vb) | ||
| 485 | { | ||
| 486 | struct bdisp_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); | ||
| 487 | |||
| 488 | /* return to V4L2 any 0-size buffer so it can be dequeued by user */ | ||
| 489 | if (!vb2_get_plane_payload(vb, 0)) { | ||
| 490 | dev_dbg(ctx->bdisp_dev->dev, "0 data buffer, skip it\n"); | ||
| 491 | vb2_buffer_done(vb, VB2_BUF_STATE_DONE); | ||
| 492 | return; | ||
| 493 | } | ||
| 494 | |||
| 495 | if (ctx->fh.m2m_ctx) | ||
| 496 | v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vb); | ||
| 497 | } | ||
| 498 | |||
| 499 | static int bdisp_start_streaming(struct vb2_queue *q, unsigned int count) | ||
| 500 | { | ||
| 501 | struct bdisp_ctx *ctx = q->drv_priv; | ||
| 502 | struct vb2_buffer *buf; | ||
| 503 | int ret = pm_runtime_get_sync(ctx->bdisp_dev->dev); | ||
| 504 | |||
| 505 | if (ret < 0) { | ||
| 506 | dev_err(ctx->bdisp_dev->dev, "failed to set runtime PM\n"); | ||
| 507 | |||
| 508 | if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) { | ||
| 509 | while ((buf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx))) | ||
| 510 | v4l2_m2m_buf_done(buf, VB2_BUF_STATE_QUEUED); | ||
| 511 | } else { | ||
| 512 | while ((buf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx))) | ||
| 513 | v4l2_m2m_buf_done(buf, VB2_BUF_STATE_QUEUED); | ||
| 514 | } | ||
| 515 | |||
| 516 | return ret; | ||
| 517 | } | ||
| 518 | |||
| 519 | return 0; | ||
| 520 | } | ||
| 521 | |||
| 522 | static void bdisp_stop_streaming(struct vb2_queue *q) | ||
| 523 | { | ||
| 524 | struct bdisp_ctx *ctx = q->drv_priv; | ||
| 525 | |||
| 526 | __bdisp_job_abort(ctx); | ||
| 527 | |||
| 528 | pm_runtime_put(ctx->bdisp_dev->dev); | ||
| 529 | } | ||
| 530 | |||
| 531 | static struct vb2_ops bdisp_qops = { | ||
| 532 | .queue_setup = bdisp_queue_setup, | ||
| 533 | .buf_prepare = bdisp_buf_prepare, | ||
| 534 | .buf_queue = bdisp_buf_queue, | ||
| 535 | .wait_prepare = vb2_ops_wait_prepare, | ||
| 536 | .wait_finish = vb2_ops_wait_finish, | ||
| 537 | .stop_streaming = bdisp_stop_streaming, | ||
| 538 | .start_streaming = bdisp_start_streaming, | ||
| 539 | }; | ||
| 540 | |||
| 541 | static int queue_init(void *priv, | ||
| 542 | struct vb2_queue *src_vq, struct vb2_queue *dst_vq) | ||
| 543 | { | ||
| 544 | struct bdisp_ctx *ctx = priv; | ||
| 545 | int ret; | ||
| 546 | |||
| 547 | memset(src_vq, 0, sizeof(*src_vq)); | ||
| 548 | src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; | ||
| 549 | src_vq->io_modes = VB2_MMAP | VB2_DMABUF; | ||
| 550 | src_vq->drv_priv = ctx; | ||
| 551 | src_vq->ops = &bdisp_qops; | ||
| 552 | src_vq->mem_ops = &vb2_dma_contig_memops; | ||
| 553 | src_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); | ||
| 554 | src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; | ||
| 555 | src_vq->lock = &ctx->bdisp_dev->lock; | ||
| 556 | |||
| 557 | ret = vb2_queue_init(src_vq); | ||
| 558 | if (ret) | ||
| 559 | return ret; | ||
| 560 | |||
| 561 | memset(dst_vq, 0, sizeof(*dst_vq)); | ||
| 562 | dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; | ||
| 563 | dst_vq->io_modes = VB2_MMAP | VB2_DMABUF; | ||
| 564 | dst_vq->drv_priv = ctx; | ||
| 565 | dst_vq->ops = &bdisp_qops; | ||
| 566 | dst_vq->mem_ops = &vb2_dma_contig_memops; | ||
| 567 | dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); | ||
| 568 | dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; | ||
| 569 | dst_vq->lock = &ctx->bdisp_dev->lock; | ||
| 570 | |||
| 571 | return vb2_queue_init(dst_vq); | ||
| 572 | } | ||
| 573 | |||
| 574 | static int bdisp_open(struct file *file) | ||
| 575 | { | ||
| 576 | struct bdisp_dev *bdisp = video_drvdata(file); | ||
| 577 | struct bdisp_ctx *ctx = NULL; | ||
| 578 | int ret; | ||
| 579 | |||
| 580 | if (mutex_lock_interruptible(&bdisp->lock)) | ||
| 581 | return -ERESTARTSYS; | ||
| 582 | |||
| 583 | /* Allocate memory for both context and node */ | ||
| 584 | ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); | ||
| 585 | if (!ctx) { | ||
| 586 | ret = -ENOMEM; | ||
| 587 | goto unlock; | ||
| 588 | } | ||
| 589 | ctx->bdisp_dev = bdisp; | ||
| 590 | |||
| 591 | if (bdisp_hw_alloc_nodes(ctx)) { | ||
| 592 | dev_err(bdisp->dev, "no memory for nodes\n"); | ||
| 593 | ret = -ENOMEM; | ||
| 594 | goto mem_ctx; | ||
| 595 | } | ||
| 596 | |||
| 597 | v4l2_fh_init(&ctx->fh, bdisp->m2m.vdev); | ||
| 598 | |||
| 599 | ret = bdisp_ctrls_create(ctx); | ||
| 600 | if (ret) { | ||
| 601 | dev_err(bdisp->dev, "Failed to create control\n"); | ||
| 602 | goto error_fh; | ||
| 603 | } | ||
| 604 | |||
| 605 | /* Use separate control handler per file handle */ | ||
| 606 | ctx->fh.ctrl_handler = &ctx->ctrl_handler; | ||
| 607 | file->private_data = &ctx->fh; | ||
| 608 | v4l2_fh_add(&ctx->fh); | ||
| 609 | |||
| 610 | /* Default format */ | ||
| 611 | ctx->src = bdisp_dflt_fmt; | ||
| 612 | ctx->dst = bdisp_dflt_fmt; | ||
| 613 | |||
| 614 | /* Setup the device context for mem2mem mode. */ | ||
| 615 | ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(bdisp->m2m.m2m_dev, ctx, | ||
| 616 | queue_init); | ||
| 617 | if (IS_ERR(ctx->fh.m2m_ctx)) { | ||
| 618 | dev_err(bdisp->dev, "Failed to initialize m2m context\n"); | ||
| 619 | ret = PTR_ERR(ctx->fh.m2m_ctx); | ||
| 620 | goto error_ctrls; | ||
| 621 | } | ||
| 622 | |||
| 623 | bdisp->m2m.refcnt++; | ||
| 624 | set_bit(ST_M2M_OPEN, &bdisp->state); | ||
| 625 | |||
| 626 | dev_dbg(bdisp->dev, "driver opened, ctx = 0x%p\n", ctx); | ||
| 627 | |||
| 628 | mutex_unlock(&bdisp->lock); | ||
| 629 | |||
| 630 | return 0; | ||
| 631 | |||
| 632 | error_ctrls: | ||
| 633 | bdisp_ctrls_delete(ctx); | ||
| 634 | error_fh: | ||
| 635 | v4l2_fh_del(&ctx->fh); | ||
| 636 | v4l2_fh_exit(&ctx->fh); | ||
| 637 | bdisp_hw_free_nodes(ctx); | ||
| 638 | mem_ctx: | ||
| 639 | kfree(ctx); | ||
| 640 | unlock: | ||
| 641 | mutex_unlock(&bdisp->lock); | ||
| 642 | |||
| 643 | return ret; | ||
| 644 | } | ||
| 645 | |||
| 646 | static int bdisp_release(struct file *file) | ||
| 647 | { | ||
| 648 | struct bdisp_ctx *ctx = fh_to_ctx(file->private_data); | ||
| 649 | struct bdisp_dev *bdisp = ctx->bdisp_dev; | ||
| 650 | |||
| 651 | dev_dbg(bdisp->dev, "%s\n", __func__); | ||
| 652 | |||
| 653 | if (mutex_lock_interruptible(&bdisp->lock)) | ||
| 654 | return -ERESTARTSYS; | ||
| 655 | |||
| 656 | v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); | ||
| 657 | |||
| 658 | bdisp_ctrls_delete(ctx); | ||
| 659 | |||
| 660 | v4l2_fh_del(&ctx->fh); | ||
| 661 | v4l2_fh_exit(&ctx->fh); | ||
| 662 | |||
| 663 | if (--bdisp->m2m.refcnt <= 0) | ||
| 664 | clear_bit(ST_M2M_OPEN, &bdisp->state); | ||
| 665 | |||
| 666 | bdisp_hw_free_nodes(ctx); | ||
| 667 | |||
| 668 | kfree(ctx); | ||
| 669 | |||
| 670 | mutex_unlock(&bdisp->lock); | ||
| 671 | |||
| 672 | return 0; | ||
| 673 | } | ||
| 674 | |||
| 675 | static const struct v4l2_file_operations bdisp_fops = { | ||
| 676 | .owner = THIS_MODULE, | ||
| 677 | .open = bdisp_open, | ||
| 678 | .release = bdisp_release, | ||
| 679 | .poll = v4l2_m2m_fop_poll, | ||
| 680 | .unlocked_ioctl = video_ioctl2, | ||
| 681 | .mmap = v4l2_m2m_fop_mmap, | ||
| 682 | }; | ||
| 683 | |||
| 684 | static int bdisp_querycap(struct file *file, void *fh, | ||
| 685 | struct v4l2_capability *cap) | ||
| 686 | { | ||
| 687 | struct bdisp_ctx *ctx = fh_to_ctx(fh); | ||
| 688 | struct bdisp_dev *bdisp = ctx->bdisp_dev; | ||
| 689 | |||
| 690 | strlcpy(cap->driver, bdisp->pdev->name, sizeof(cap->driver)); | ||
| 691 | strlcpy(cap->card, bdisp->pdev->name, sizeof(cap->card)); | ||
| 692 | snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s%d", | ||
| 693 | BDISP_NAME, bdisp->id); | ||
| 694 | |||
| 695 | cap->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_M2M; | ||
| 696 | |||
| 697 | cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS; | ||
| 698 | |||
| 699 | return 0; | ||
| 700 | } | ||
| 701 | |||
| 702 | static int bdisp_enum_fmt(struct file *file, void *fh, struct v4l2_fmtdesc *f) | ||
| 703 | { | ||
| 704 | struct bdisp_ctx *ctx = fh_to_ctx(fh); | ||
| 705 | const struct bdisp_fmt *fmt; | ||
| 706 | |||
| 707 | if (f->index >= ARRAY_SIZE(bdisp_formats)) | ||
| 708 | return -EINVAL; | ||
| 709 | |||
| 710 | fmt = &bdisp_formats[f->index]; | ||
| 711 | |||
| 712 | if ((fmt->pixelformat == V4L2_PIX_FMT_YUV420) && | ||
| 713 | (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)) { | ||
| 714 | dev_dbg(ctx->bdisp_dev->dev, "No YU12 on capture\n"); | ||
| 715 | return -EINVAL; | ||
| 716 | } | ||
| 717 | f->pixelformat = fmt->pixelformat; | ||
| 718 | |||
| 719 | return 0; | ||
| 720 | } | ||
| 721 | |||
| 722 | static int bdisp_g_fmt(struct file *file, void *fh, struct v4l2_format *f) | ||
| 723 | { | ||
| 724 | struct bdisp_ctx *ctx = fh_to_ctx(fh); | ||
| 725 | struct v4l2_pix_format *pix = &f->fmt.pix; | ||
| 726 | struct bdisp_frame *frame = ctx_get_frame(ctx, f->type); | ||
| 727 | |||
| 728 | if (IS_ERR(frame)) { | ||
| 729 | dev_err(ctx->bdisp_dev->dev, "Invalid frame (%p)\n", frame); | ||
| 730 | return PTR_ERR(frame); | ||
| 731 | } | ||
| 732 | |||
| 733 | pix = &f->fmt.pix; | ||
| 734 | pix->width = frame->width; | ||
| 735 | pix->height = frame->height; | ||
| 736 | pix->pixelformat = frame->fmt->pixelformat; | ||
| 737 | pix->field = frame->field; | ||
| 738 | pix->bytesperline = frame->bytesperline; | ||
| 739 | pix->sizeimage = frame->sizeimage; | ||
| 740 | pix->colorspace = (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) ? | ||
| 741 | frame->colorspace : bdisp_dflt_fmt.colorspace; | ||
| 742 | |||
| 743 | return 0; | ||
| 744 | } | ||
| 745 | |||
| 746 | static int bdisp_try_fmt(struct file *file, void *fh, struct v4l2_format *f) | ||
| 747 | { | ||
| 748 | struct bdisp_ctx *ctx = fh_to_ctx(fh); | ||
| 749 | struct v4l2_pix_format *pix = &f->fmt.pix; | ||
| 750 | const struct bdisp_fmt *format; | ||
| 751 | u32 in_w, in_h; | ||
| 752 | |||
| 753 | format = bdisp_find_fmt(pix->pixelformat); | ||
| 754 | if (!format) { | ||
| 755 | dev_dbg(ctx->bdisp_dev->dev, "Unknown format 0x%x\n", | ||
| 756 | pix->pixelformat); | ||
| 757 | return -EINVAL; | ||
| 758 | } | ||
| 759 | |||
| 760 | /* YUV420P only supported for VIDEO_OUTPUT */ | ||
| 761 | if ((format->pixelformat == V4L2_PIX_FMT_YUV420) && | ||
| 762 | (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)) { | ||
| 763 | dev_dbg(ctx->bdisp_dev->dev, "No YU12 on capture\n"); | ||
| 764 | return -EINVAL; | ||
| 765 | } | ||
| 766 | |||
| 767 | /* Field (interlaced only supported on OUTPUT) */ | ||
| 768 | if ((f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) || | ||
| 769 | (pix->field != V4L2_FIELD_INTERLACED)) | ||
| 770 | pix->field = V4L2_FIELD_NONE; | ||
| 771 | |||
| 772 | /* Adjust width & height */ | ||
| 773 | in_w = pix->width; | ||
| 774 | in_h = pix->height; | ||
| 775 | v4l_bound_align_image(&pix->width, | ||
| 776 | BDISP_MIN_W, BDISP_MAX_W, | ||
| 777 | ffs(format->w_align) - 1, | ||
| 778 | &pix->height, | ||
| 779 | BDISP_MIN_H, BDISP_MAX_H, | ||
| 780 | ffs(format->h_align) - 1, | ||
| 781 | 0); | ||
| 782 | if ((pix->width != in_w) || (pix->height != in_h)) | ||
| 783 | dev_dbg(ctx->bdisp_dev->dev, | ||
| 784 | "%s size updated: %dx%d -> %dx%d\n", __func__, | ||
| 785 | in_w, in_h, pix->width, pix->height); | ||
| 786 | |||
| 787 | pix->bytesperline = (pix->width * format->bpp_plane0) / 8; | ||
| 788 | pix->sizeimage = (pix->width * pix->height * format->bpp) / 8; | ||
| 789 | |||
| 790 | if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) | ||
| 791 | pix->colorspace = bdisp_dflt_fmt.colorspace; | ||
| 792 | |||
| 793 | return 0; | ||
| 794 | } | ||
| 795 | |||
| 796 | static int bdisp_s_fmt(struct file *file, void *fh, struct v4l2_format *f) | ||
| 797 | { | ||
| 798 | struct bdisp_ctx *ctx = fh_to_ctx(fh); | ||
| 799 | struct vb2_queue *vq; | ||
| 800 | struct bdisp_frame *frame; | ||
| 801 | struct v4l2_pix_format *pix; | ||
| 802 | int ret; | ||
| 803 | u32 state; | ||
| 804 | |||
| 805 | ret = bdisp_try_fmt(file, fh, f); | ||
| 806 | if (ret) { | ||
| 807 | dev_err(ctx->bdisp_dev->dev, "Cannot set format\n"); | ||
| 808 | return ret; | ||
| 809 | } | ||
| 810 | |||
| 811 | vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); | ||
| 812 | if (vb2_is_streaming(vq)) { | ||
| 813 | dev_err(ctx->bdisp_dev->dev, "queue (%d) busy\n", f->type); | ||
| 814 | return -EBUSY; | ||
| 815 | } | ||
| 816 | |||
| 817 | frame = (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) ? | ||
| 818 | &ctx->src : &ctx->dst; | ||
| 819 | pix = &f->fmt.pix; | ||
| 820 | frame->fmt = bdisp_find_fmt(pix->pixelformat); | ||
| 821 | if (!frame->fmt) { | ||
| 822 | dev_err(ctx->bdisp_dev->dev, "Unknown format 0x%x\n", | ||
| 823 | pix->pixelformat); | ||
| 824 | return -EINVAL; | ||
| 825 | } | ||
| 826 | |||
| 827 | frame->width = pix->width; | ||
| 828 | frame->height = pix->height; | ||
| 829 | frame->bytesperline = pix->bytesperline; | ||
| 830 | frame->sizeimage = pix->sizeimage; | ||
| 831 | frame->field = pix->field; | ||
| 832 | if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) | ||
| 833 | frame->colorspace = pix->colorspace; | ||
| 834 | |||
| 835 | frame->crop.width = frame->width; | ||
| 836 | frame->crop.height = frame->height; | ||
| 837 | frame->crop.left = 0; | ||
| 838 | frame->crop.top = 0; | ||
| 839 | |||
| 840 | state = BDISP_PARAMS; | ||
| 841 | state |= (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) ? | ||
| 842 | BDISP_DST_FMT : BDISP_SRC_FMT; | ||
| 843 | bdisp_ctx_state_lock_set(state, ctx); | ||
| 844 | |||
| 845 | return 0; | ||
| 846 | } | ||
| 847 | |||
| 848 | static int bdisp_g_selection(struct file *file, void *fh, | ||
| 849 | struct v4l2_selection *s) | ||
| 850 | { | ||
| 851 | struct bdisp_frame *frame; | ||
| 852 | struct bdisp_ctx *ctx = fh_to_ctx(fh); | ||
| 853 | |||
| 854 | if (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) { | ||
| 855 | /* Composing / capture is not supported */ | ||
| 856 | dev_dbg(ctx->bdisp_dev->dev, "Not supported for capture\n"); | ||
| 857 | return -EINVAL; | ||
| 858 | } | ||
| 859 | |||
| 860 | frame = ctx_get_frame(ctx, s->type); | ||
| 861 | if (IS_ERR(frame)) { | ||
| 862 | dev_err(ctx->bdisp_dev->dev, "Invalid frame (%p)\n", frame); | ||
| 863 | return PTR_ERR(frame); | ||
| 864 | } | ||
| 865 | |||
| 866 | switch (s->target) { | ||
| 867 | case V4L2_SEL_TGT_CROP: | ||
| 868 | /* cropped frame */ | ||
| 869 | s->r = frame->crop; | ||
| 870 | break; | ||
| 871 | case V4L2_SEL_TGT_CROP_DEFAULT: | ||
| 872 | case V4L2_SEL_TGT_CROP_BOUNDS: | ||
| 873 | /* complete frame */ | ||
| 874 | s->r.left = 0; | ||
| 875 | s->r.top = 0; | ||
| 876 | s->r.width = frame->width; | ||
| 877 | s->r.height = frame->height; | ||
| 878 | break; | ||
| 879 | default: | ||
| 880 | dev_dbg(ctx->bdisp_dev->dev, "Invalid target\n"); | ||
| 881 | return -EINVAL; | ||
| 882 | } | ||
| 883 | |||
| 884 | return 0; | ||
| 885 | } | ||
| 886 | |||
| 887 | static int is_rect_enclosed(struct v4l2_rect *a, struct v4l2_rect *b) | ||
| 888 | { | ||
| 889 | /* Return 1 if a is enclosed in b, or 0 otherwise. */ | ||
| 890 | |||
| 891 | if (a->left < b->left || a->top < b->top) | ||
| 892 | return 0; | ||
| 893 | |||
| 894 | if (a->left + a->width > b->left + b->width) | ||
| 895 | return 0; | ||
| 896 | |||
| 897 | if (a->top + a->height > b->top + b->height) | ||
| 898 | return 0; | ||
| 899 | |||
| 900 | return 1; | ||
| 901 | } | ||
| 902 | |||
| 903 | static int bdisp_s_selection(struct file *file, void *fh, | ||
| 904 | struct v4l2_selection *s) | ||
| 905 | { | ||
| 906 | struct bdisp_frame *frame; | ||
| 907 | struct bdisp_ctx *ctx = fh_to_ctx(fh); | ||
| 908 | struct v4l2_rect *in, out; | ||
| 909 | |||
| 910 | if (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) { | ||
| 911 | /* Composing / capture is not supported */ | ||
| 912 | dev_dbg(ctx->bdisp_dev->dev, "Not supported for capture\n"); | ||
| 913 | return -EINVAL; | ||
| 914 | } | ||
| 915 | |||
| 916 | if (s->target != V4L2_SEL_TGT_CROP) { | ||
| 917 | dev_dbg(ctx->bdisp_dev->dev, "Invalid target\n"); | ||
| 918 | return -EINVAL; | ||
| 919 | } | ||
| 920 | |||
| 921 | frame = ctx_get_frame(ctx, s->type); | ||
| 922 | if (IS_ERR(frame)) { | ||
| 923 | dev_err(ctx->bdisp_dev->dev, "Invalid frame (%p)\n", frame); | ||
| 924 | return PTR_ERR(frame); | ||
| 925 | } | ||
| 926 | |||
| 927 | in = &s->r; | ||
| 928 | out = *in; | ||
| 929 | |||
| 930 | /* Align and check origin */ | ||
| 931 | out.left = ALIGN(in->left, frame->fmt->w_align); | ||
| 932 | out.top = ALIGN(in->top, frame->fmt->h_align); | ||
| 933 | |||
| 934 | if ((out.left < 0) || (out.left >= frame->width) || | ||
| 935 | (out.top < 0) || (out.top >= frame->height)) { | ||
| 936 | dev_err(ctx->bdisp_dev->dev, | ||
| 937 | "Invalid crop: %dx%d@(%d,%d) vs frame: %dx%d\n", | ||
| 938 | out.width, out.height, out.left, out.top, | ||
| 939 | frame->width, frame->height); | ||
| 940 | return -EINVAL; | ||
| 941 | } | ||
| 942 | |||
| 943 | /* Align and check size */ | ||
| 944 | out.width = ALIGN(in->width, frame->fmt->w_align); | ||
| 945 | out.height = ALIGN(in->height, frame->fmt->w_align); | ||
| 946 | |||
| 947 | if (((out.left + out.width) > frame->width) || | ||
| 948 | ((out.top + out.height) > frame->height)) { | ||
| 949 | dev_err(ctx->bdisp_dev->dev, | ||
| 950 | "Invalid crop: %dx%d@(%d,%d) vs frame: %dx%d\n", | ||
| 951 | out.width, out.height, out.left, out.top, | ||
| 952 | frame->width, frame->height); | ||
| 953 | return -EINVAL; | ||
| 954 | } | ||
| 955 | |||
| 956 | /* Checks adjust constraints flags */ | ||
| 957 | if (s->flags & V4L2_SEL_FLAG_LE && !is_rect_enclosed(&out, in)) | ||
| 958 | return -ERANGE; | ||
| 959 | |||
| 960 | if (s->flags & V4L2_SEL_FLAG_GE && !is_rect_enclosed(in, &out)) | ||
| 961 | return -ERANGE; | ||
| 962 | |||
| 963 | if ((out.left != in->left) || (out.top != in->top) || | ||
| 964 | (out.width != in->width) || (out.height != in->height)) { | ||
| 965 | dev_dbg(ctx->bdisp_dev->dev, | ||
| 966 | "%s crop updated: %dx%d@(%d,%d) -> %dx%d@(%d,%d)\n", | ||
| 967 | __func__, in->width, in->height, in->left, in->top, | ||
| 968 | out.width, out.height, out.left, out.top); | ||
| 969 | *in = out; | ||
| 970 | } | ||
| 971 | |||
| 972 | frame->crop = out; | ||
| 973 | |||
| 974 | bdisp_ctx_state_lock_set(BDISP_PARAMS, ctx); | ||
| 975 | |||
| 976 | return 0; | ||
| 977 | } | ||
| 978 | |||
| 979 | static int bdisp_streamon(struct file *file, void *fh, enum v4l2_buf_type type) | ||
| 980 | { | ||
| 981 | struct bdisp_ctx *ctx = fh_to_ctx(fh); | ||
| 982 | |||
| 983 | if ((type == V4L2_BUF_TYPE_VIDEO_OUTPUT) && | ||
| 984 | !bdisp_ctx_state_is_set(BDISP_SRC_FMT, ctx)) { | ||
| 985 | dev_err(ctx->bdisp_dev->dev, "src not defined\n"); | ||
| 986 | return -EINVAL; | ||
| 987 | } | ||
| 988 | |||
| 989 | if ((type == V4L2_BUF_TYPE_VIDEO_CAPTURE) && | ||
| 990 | !bdisp_ctx_state_is_set(BDISP_DST_FMT, ctx)) { | ||
| 991 | dev_err(ctx->bdisp_dev->dev, "dst not defined\n"); | ||
| 992 | return -EINVAL; | ||
| 993 | } | ||
| 994 | |||
| 995 | return v4l2_m2m_streamon(file, ctx->fh.m2m_ctx, type); | ||
| 996 | } | ||
| 997 | |||
| 998 | static const struct v4l2_ioctl_ops bdisp_ioctl_ops = { | ||
| 999 | .vidioc_querycap = bdisp_querycap, | ||
| 1000 | .vidioc_enum_fmt_vid_cap = bdisp_enum_fmt, | ||
| 1001 | .vidioc_enum_fmt_vid_out = bdisp_enum_fmt, | ||
| 1002 | .vidioc_g_fmt_vid_cap = bdisp_g_fmt, | ||
| 1003 | .vidioc_g_fmt_vid_out = bdisp_g_fmt, | ||
| 1004 | .vidioc_try_fmt_vid_cap = bdisp_try_fmt, | ||
| 1005 | .vidioc_try_fmt_vid_out = bdisp_try_fmt, | ||
| 1006 | .vidioc_s_fmt_vid_cap = bdisp_s_fmt, | ||
| 1007 | .vidioc_s_fmt_vid_out = bdisp_s_fmt, | ||
| 1008 | .vidioc_g_selection = bdisp_g_selection, | ||
| 1009 | .vidioc_s_selection = bdisp_s_selection, | ||
| 1010 | .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs, | ||
| 1011 | .vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs, | ||
| 1012 | .vidioc_expbuf = v4l2_m2m_ioctl_expbuf, | ||
| 1013 | .vidioc_querybuf = v4l2_m2m_ioctl_querybuf, | ||
| 1014 | .vidioc_qbuf = v4l2_m2m_ioctl_qbuf, | ||
| 1015 | .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf, | ||
| 1016 | .vidioc_streamon = bdisp_streamon, | ||
| 1017 | .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, | ||
| 1018 | .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, | ||
| 1019 | .vidioc_unsubscribe_event = v4l2_event_unsubscribe, | ||
| 1020 | }; | ||
| 1021 | |||
| 1022 | static int bdisp_register_device(struct bdisp_dev *bdisp) | ||
| 1023 | { | ||
| 1024 | int ret; | ||
| 1025 | |||
| 1026 | if (!bdisp) | ||
| 1027 | return -ENODEV; | ||
| 1028 | |||
| 1029 | bdisp->vdev.fops = &bdisp_fops; | ||
| 1030 | bdisp->vdev.ioctl_ops = &bdisp_ioctl_ops; | ||
| 1031 | bdisp->vdev.release = video_device_release_empty; | ||
| 1032 | bdisp->vdev.lock = &bdisp->lock; | ||
| 1033 | bdisp->vdev.vfl_dir = VFL_DIR_M2M; | ||
| 1034 | bdisp->vdev.v4l2_dev = &bdisp->v4l2_dev; | ||
| 1035 | snprintf(bdisp->vdev.name, sizeof(bdisp->vdev.name), "%s.%d", | ||
| 1036 | BDISP_NAME, bdisp->id); | ||
| 1037 | |||
| 1038 | video_set_drvdata(&bdisp->vdev, bdisp); | ||
| 1039 | |||
| 1040 | bdisp->m2m.vdev = &bdisp->vdev; | ||
| 1041 | bdisp->m2m.m2m_dev = v4l2_m2m_init(&bdisp_m2m_ops); | ||
| 1042 | if (IS_ERR(bdisp->m2m.m2m_dev)) { | ||
| 1043 | dev_err(bdisp->dev, "failed to initialize v4l2-m2m device\n"); | ||
| 1044 | return PTR_ERR(bdisp->m2m.m2m_dev); | ||
| 1045 | } | ||
| 1046 | |||
| 1047 | ret = video_register_device(&bdisp->vdev, VFL_TYPE_GRABBER, -1); | ||
| 1048 | if (ret) { | ||
| 1049 | dev_err(bdisp->dev, | ||
| 1050 | "%s(): failed to register video device\n", __func__); | ||
| 1051 | v4l2_m2m_release(bdisp->m2m.m2m_dev); | ||
| 1052 | return ret; | ||
| 1053 | } | ||
| 1054 | |||
| 1055 | return 0; | ||
| 1056 | } | ||
| 1057 | |||
| 1058 | static void bdisp_unregister_device(struct bdisp_dev *bdisp) | ||
| 1059 | { | ||
| 1060 | if (!bdisp) | ||
| 1061 | return; | ||
| 1062 | |||
| 1063 | if (bdisp->m2m.m2m_dev) | ||
| 1064 | v4l2_m2m_release(bdisp->m2m.m2m_dev); | ||
| 1065 | |||
| 1066 | video_unregister_device(bdisp->m2m.vdev); | ||
| 1067 | } | ||
| 1068 | |||
| 1069 | static irqreturn_t bdisp_irq_thread(int irq, void *priv) | ||
| 1070 | { | ||
| 1071 | struct bdisp_dev *bdisp = priv; | ||
| 1072 | struct bdisp_ctx *ctx; | ||
| 1073 | |||
| 1074 | spin_lock(&bdisp->slock); | ||
| 1075 | |||
| 1076 | bdisp_dbg_perf_end(bdisp); | ||
| 1077 | |||
| 1078 | cancel_delayed_work(&bdisp->timeout_work); | ||
| 1079 | |||
| 1080 | if (!test_and_clear_bit(ST_M2M_RUNNING, &bdisp->state)) | ||
| 1081 | goto isr_unlock; | ||
| 1082 | |||
| 1083 | if (test_and_clear_bit(ST_M2M_SUSPENDING, &bdisp->state)) { | ||
| 1084 | set_bit(ST_M2M_SUSPENDED, &bdisp->state); | ||
| 1085 | wake_up(&bdisp->irq_queue); | ||
| 1086 | goto isr_unlock; | ||
| 1087 | } | ||
| 1088 | |||
| 1089 | ctx = v4l2_m2m_get_curr_priv(bdisp->m2m.m2m_dev); | ||
| 1090 | if (!ctx || !ctx->fh.m2m_ctx) | ||
| 1091 | goto isr_unlock; | ||
| 1092 | |||
| 1093 | spin_unlock(&bdisp->slock); | ||
| 1094 | |||
| 1095 | bdisp_job_finish(ctx, VB2_BUF_STATE_DONE); | ||
| 1096 | |||
| 1097 | if (bdisp_ctx_state_is_set(BDISP_CTX_STOP_REQ, ctx)) { | ||
| 1098 | bdisp_ctx_state_lock_clear(BDISP_CTX_STOP_REQ, ctx); | ||
| 1099 | wake_up(&bdisp->irq_queue); | ||
| 1100 | } | ||
| 1101 | |||
| 1102 | return IRQ_HANDLED; | ||
| 1103 | |||
| 1104 | isr_unlock: | ||
| 1105 | spin_unlock(&bdisp->slock); | ||
| 1106 | |||
| 1107 | return IRQ_HANDLED; | ||
| 1108 | } | ||
| 1109 | |||
| 1110 | static irqreturn_t bdisp_irq_handler(int irq, void *priv) | ||
| 1111 | { | ||
| 1112 | if (bdisp_hw_get_and_clear_irq((struct bdisp_dev *)priv)) | ||
| 1113 | return IRQ_NONE; | ||
| 1114 | else | ||
| 1115 | return IRQ_WAKE_THREAD; | ||
| 1116 | } | ||
| 1117 | |||
| 1118 | static void bdisp_irq_timeout(struct work_struct *ptr) | ||
| 1119 | { | ||
| 1120 | struct delayed_work *twork = to_delayed_work(ptr); | ||
| 1121 | struct bdisp_dev *bdisp = container_of(twork, struct bdisp_dev, | ||
| 1122 | timeout_work); | ||
| 1123 | struct bdisp_ctx *ctx; | ||
| 1124 | |||
| 1125 | ctx = v4l2_m2m_get_curr_priv(bdisp->m2m.m2m_dev); | ||
| 1126 | |||
| 1127 | dev_err(ctx->bdisp_dev->dev, "Device work timeout\n"); | ||
| 1128 | |||
| 1129 | spin_lock(&bdisp->slock); | ||
| 1130 | clear_bit(ST_M2M_RUNNING, &bdisp->state); | ||
| 1131 | spin_unlock(&bdisp->slock); | ||
| 1132 | |||
| 1133 | bdisp_hw_reset(bdisp); | ||
| 1134 | |||
| 1135 | bdisp_job_finish(ctx, VB2_BUF_STATE_ERROR); | ||
| 1136 | } | ||
| 1137 | |||
| 1138 | static int bdisp_m2m_suspend(struct bdisp_dev *bdisp) | ||
| 1139 | { | ||
| 1140 | unsigned long flags; | ||
| 1141 | int timeout; | ||
| 1142 | |||
| 1143 | spin_lock_irqsave(&bdisp->slock, flags); | ||
| 1144 | if (!test_bit(ST_M2M_RUNNING, &bdisp->state)) { | ||
| 1145 | spin_unlock_irqrestore(&bdisp->slock, flags); | ||
| 1146 | return 0; | ||
| 1147 | } | ||
| 1148 | clear_bit(ST_M2M_SUSPENDED, &bdisp->state); | ||
| 1149 | set_bit(ST_M2M_SUSPENDING, &bdisp->state); | ||
| 1150 | spin_unlock_irqrestore(&bdisp->slock, flags); | ||
| 1151 | |||
| 1152 | timeout = wait_event_timeout(bdisp->irq_queue, | ||
| 1153 | test_bit(ST_M2M_SUSPENDED, &bdisp->state), | ||
| 1154 | BDISP_WORK_TIMEOUT); | ||
| 1155 | |||
| 1156 | clear_bit(ST_M2M_SUSPENDING, &bdisp->state); | ||
| 1157 | |||
| 1158 | if (!timeout) { | ||
| 1159 | dev_err(bdisp->dev, "%s IRQ timeout\n", __func__); | ||
| 1160 | return -EAGAIN; | ||
| 1161 | } | ||
| 1162 | |||
| 1163 | return 0; | ||
| 1164 | } | ||
| 1165 | |||
| 1166 | static int bdisp_m2m_resume(struct bdisp_dev *bdisp) | ||
| 1167 | { | ||
| 1168 | struct bdisp_ctx *ctx; | ||
| 1169 | unsigned long flags; | ||
| 1170 | |||
| 1171 | spin_lock_irqsave(&bdisp->slock, flags); | ||
| 1172 | ctx = bdisp->m2m.ctx; | ||
| 1173 | bdisp->m2m.ctx = NULL; | ||
| 1174 | spin_unlock_irqrestore(&bdisp->slock, flags); | ||
| 1175 | |||
| 1176 | if (test_and_clear_bit(ST_M2M_SUSPENDED, &bdisp->state)) | ||
| 1177 | bdisp_job_finish(ctx, VB2_BUF_STATE_ERROR); | ||
| 1178 | |||
| 1179 | return 0; | ||
| 1180 | } | ||
| 1181 | |||
| 1182 | static int bdisp_runtime_resume(struct device *dev) | ||
| 1183 | { | ||
| 1184 | struct bdisp_dev *bdisp = dev_get_drvdata(dev); | ||
| 1185 | int ret = clk_enable(bdisp->clock); | ||
| 1186 | |||
| 1187 | if (ret) | ||
| 1188 | return ret; | ||
| 1189 | |||
| 1190 | return bdisp_m2m_resume(bdisp); | ||
| 1191 | } | ||
| 1192 | |||
| 1193 | static int bdisp_runtime_suspend(struct device *dev) | ||
| 1194 | { | ||
| 1195 | struct bdisp_dev *bdisp = dev_get_drvdata(dev); | ||
| 1196 | int ret = bdisp_m2m_suspend(bdisp); | ||
| 1197 | |||
| 1198 | if (!ret) | ||
| 1199 | clk_disable(bdisp->clock); | ||
| 1200 | |||
| 1201 | return ret; | ||
| 1202 | } | ||
| 1203 | |||
| 1204 | static int bdisp_resume(struct device *dev) | ||
| 1205 | { | ||
| 1206 | struct bdisp_dev *bdisp = dev_get_drvdata(dev); | ||
| 1207 | unsigned long flags; | ||
| 1208 | int opened; | ||
| 1209 | |||
| 1210 | spin_lock_irqsave(&bdisp->slock, flags); | ||
| 1211 | opened = test_bit(ST_M2M_OPEN, &bdisp->state); | ||
| 1212 | spin_unlock_irqrestore(&bdisp->slock, flags); | ||
| 1213 | |||
| 1214 | if (!opened) | ||
| 1215 | return 0; | ||
| 1216 | |||
| 1217 | if (!pm_runtime_suspended(dev)) | ||
| 1218 | return bdisp_runtime_resume(dev); | ||
| 1219 | |||
| 1220 | return 0; | ||
| 1221 | } | ||
| 1222 | |||
| 1223 | static int bdisp_suspend(struct device *dev) | ||
| 1224 | { | ||
| 1225 | if (!pm_runtime_suspended(dev)) | ||
| 1226 | return bdisp_runtime_suspend(dev); | ||
| 1227 | |||
| 1228 | return 0; | ||
| 1229 | } | ||
| 1230 | |||
| 1231 | static const struct dev_pm_ops bdisp_pm_ops = { | ||
| 1232 | .suspend = bdisp_suspend, | ||
| 1233 | .resume = bdisp_resume, | ||
| 1234 | .runtime_suspend = bdisp_runtime_suspend, | ||
| 1235 | .runtime_resume = bdisp_runtime_resume, | ||
| 1236 | }; | ||
| 1237 | |||
| 1238 | static int bdisp_remove(struct platform_device *pdev) | ||
| 1239 | { | ||
| 1240 | struct bdisp_dev *bdisp = platform_get_drvdata(pdev); | ||
| 1241 | |||
| 1242 | bdisp_unregister_device(bdisp); | ||
| 1243 | |||
| 1244 | bdisp_hw_free_filters(bdisp->dev); | ||
| 1245 | |||
| 1246 | vb2_dma_contig_cleanup_ctx(bdisp->alloc_ctx); | ||
| 1247 | |||
| 1248 | pm_runtime_disable(&pdev->dev); | ||
| 1249 | |||
| 1250 | bdisp_debugfs_remove(bdisp); | ||
| 1251 | |||
| 1252 | v4l2_device_unregister(&bdisp->v4l2_dev); | ||
| 1253 | |||
| 1254 | if (!IS_ERR(bdisp->clock)) | ||
| 1255 | clk_unprepare(bdisp->clock); | ||
| 1256 | |||
| 1257 | dev_dbg(&pdev->dev, "%s driver unloaded\n", pdev->name); | ||
| 1258 | |||
| 1259 | return 0; | ||
| 1260 | } | ||
| 1261 | |||
| 1262 | static int bdisp_probe(struct platform_device *pdev) | ||
| 1263 | { | ||
| 1264 | struct bdisp_dev *bdisp; | ||
| 1265 | struct resource *res; | ||
| 1266 | struct device *dev = &pdev->dev; | ||
| 1267 | int ret; | ||
| 1268 | |||
| 1269 | dev_dbg(dev, "%s\n", __func__); | ||
| 1270 | |||
| 1271 | bdisp = devm_kzalloc(dev, sizeof(struct bdisp_dev), GFP_KERNEL); | ||
| 1272 | if (!bdisp) | ||
| 1273 | return -ENOMEM; | ||
| 1274 | |||
| 1275 | bdisp->pdev = pdev; | ||
| 1276 | bdisp->dev = dev; | ||
| 1277 | platform_set_drvdata(pdev, bdisp); | ||
| 1278 | |||
| 1279 | if (dev->of_node) | ||
| 1280 | bdisp->id = of_alias_get_id(pdev->dev.of_node, BDISP_NAME); | ||
| 1281 | else | ||
| 1282 | bdisp->id = pdev->id; | ||
| 1283 | |||
| 1284 | init_waitqueue_head(&bdisp->irq_queue); | ||
| 1285 | INIT_DELAYED_WORK(&bdisp->timeout_work, bdisp_irq_timeout); | ||
| 1286 | bdisp->work_queue = create_workqueue(BDISP_NAME); | ||
| 1287 | |||
| 1288 | spin_lock_init(&bdisp->slock); | ||
| 1289 | mutex_init(&bdisp->lock); | ||
| 1290 | |||
| 1291 | /* get resources */ | ||
| 1292 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
| 1293 | bdisp->regs = devm_ioremap_resource(dev, res); | ||
| 1294 | if (IS_ERR(bdisp->regs)) { | ||
| 1295 | dev_err(dev, "failed to get regs\n"); | ||
| 1296 | return PTR_ERR(bdisp->regs); | ||
| 1297 | } | ||
| 1298 | |||
| 1299 | bdisp->clock = devm_clk_get(dev, BDISP_NAME); | ||
| 1300 | if (IS_ERR(bdisp->clock)) { | ||
| 1301 | dev_err(dev, "failed to get clock\n"); | ||
| 1302 | return PTR_ERR(bdisp->clock); | ||
| 1303 | } | ||
| 1304 | |||
| 1305 | ret = clk_prepare(bdisp->clock); | ||
| 1306 | if (ret < 0) { | ||
| 1307 | dev_err(dev, "clock prepare failed\n"); | ||
| 1308 | bdisp->clock = ERR_PTR(-EINVAL); | ||
| 1309 | return ret; | ||
| 1310 | } | ||
| 1311 | |||
| 1312 | res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); | ||
| 1313 | if (!res) { | ||
| 1314 | dev_err(dev, "failed to get IRQ resource\n"); | ||
| 1315 | goto err_clk; | ||
| 1316 | } | ||
| 1317 | |||
| 1318 | ret = devm_request_threaded_irq(dev, res->start, bdisp_irq_handler, | ||
| 1319 | bdisp_irq_thread, IRQF_ONESHOT, | ||
| 1320 | pdev->name, bdisp); | ||
| 1321 | if (ret) { | ||
| 1322 | dev_err(dev, "failed to install irq\n"); | ||
| 1323 | goto err_clk; | ||
| 1324 | } | ||
| 1325 | |||
| 1326 | /* v4l2 register */ | ||
| 1327 | ret = v4l2_device_register(dev, &bdisp->v4l2_dev); | ||
| 1328 | if (ret) { | ||
| 1329 | dev_err(dev, "failed to register\n"); | ||
| 1330 | goto err_clk; | ||
| 1331 | } | ||
| 1332 | |||
| 1333 | /* Debug */ | ||
| 1334 | ret = bdisp_debugfs_create(bdisp); | ||
| 1335 | if (ret) { | ||
| 1336 | dev_err(dev, "failed to create debugfs\n"); | ||
| 1337 | goto err_v4l2; | ||
| 1338 | } | ||
| 1339 | |||
| 1340 | /* Power management */ | ||
| 1341 | pm_runtime_enable(dev); | ||
| 1342 | ret = pm_runtime_get_sync(dev); | ||
| 1343 | if (ret < 0) { | ||
| 1344 | dev_err(dev, "failed to set PM\n"); | ||
| 1345 | goto err_dbg; | ||
| 1346 | } | ||
| 1347 | |||
| 1348 | /* Continuous memory allocator */ | ||
| 1349 | bdisp->alloc_ctx = vb2_dma_contig_init_ctx(dev); | ||
| 1350 | if (IS_ERR(bdisp->alloc_ctx)) { | ||
| 1351 | ret = PTR_ERR(bdisp->alloc_ctx); | ||
| 1352 | goto err_pm; | ||
| 1353 | } | ||
| 1354 | |||
| 1355 | /* Filters */ | ||
| 1356 | if (bdisp_hw_alloc_filters(bdisp->dev)) { | ||
| 1357 | dev_err(bdisp->dev, "no memory for filters\n"); | ||
| 1358 | ret = -ENOMEM; | ||
| 1359 | goto err_vb2_dma; | ||
| 1360 | } | ||
| 1361 | |||
| 1362 | /* Register */ | ||
| 1363 | ret = bdisp_register_device(bdisp); | ||
| 1364 | if (ret) { | ||
| 1365 | dev_err(dev, "failed to register\n"); | ||
| 1366 | goto err_filter; | ||
| 1367 | } | ||
| 1368 | |||
| 1369 | dev_info(dev, "%s%d registered as /dev/video%d\n", BDISP_NAME, | ||
| 1370 | bdisp->id, bdisp->vdev.num); | ||
| 1371 | |||
| 1372 | pm_runtime_put(dev); | ||
| 1373 | |||
| 1374 | return 0; | ||
| 1375 | |||
| 1376 | err_filter: | ||
| 1377 | bdisp_hw_free_filters(bdisp->dev); | ||
| 1378 | err_vb2_dma: | ||
| 1379 | vb2_dma_contig_cleanup_ctx(bdisp->alloc_ctx); | ||
| 1380 | err_pm: | ||
| 1381 | pm_runtime_put(dev); | ||
| 1382 | err_dbg: | ||
| 1383 | bdisp_debugfs_remove(bdisp); | ||
| 1384 | err_v4l2: | ||
| 1385 | v4l2_device_unregister(&bdisp->v4l2_dev); | ||
| 1386 | err_clk: | ||
| 1387 | if (!IS_ERR(bdisp->clock)) | ||
| 1388 | clk_unprepare(bdisp->clock); | ||
| 1389 | |||
| 1390 | return ret; | ||
| 1391 | } | ||
| 1392 | |||
| 1393 | static const struct of_device_id bdisp_match_types[] = { | ||
| 1394 | { | ||
| 1395 | .compatible = "st,stih407-bdisp", | ||
| 1396 | }, | ||
| 1397 | { /* end node */ } | ||
| 1398 | }; | ||
| 1399 | |||
| 1400 | MODULE_DEVICE_TABLE(of, bdisp_match_types); | ||
| 1401 | |||
| 1402 | static struct platform_driver bdisp_driver = { | ||
| 1403 | .probe = bdisp_probe, | ||
| 1404 | .remove = bdisp_remove, | ||
| 1405 | .driver = { | ||
| 1406 | .name = BDISP_NAME, | ||
| 1407 | .of_match_table = bdisp_match_types, | ||
| 1408 | .pm = &bdisp_pm_ops, | ||
| 1409 | }, | ||
| 1410 | }; | ||
| 1411 | |||
| 1412 | module_platform_driver(bdisp_driver); | ||
| 1413 | |||
| 1414 | MODULE_DESCRIPTION("2D blitter for STMicroelectronics SoC"); | ||
| 1415 | MODULE_AUTHOR("Fabien Dessenne <fabien.dessenne@st.com>"); | ||
| 1416 | MODULE_LICENSE("GPL"); | ||
