| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Hantro VPU HEVC codec driver |
| * |
| * Copyright (C) 2020 Safran Passenger Innovations LLC |
| */ |
| |
| #include <linux/types.h> |
| #include <media/v4l2-mem2mem.h> |
| |
| #include "hantro.h" |
| #include "hantro_hw.h" |
| |
| #define VERT_FILTER_RAM_SIZE 8 /* bytes per pixel row */ |
| /* |
| * BSD control data of current picture at tile border |
| * 128 bits per 4x4 tile = 128/(8*4) bytes per row |
| */ |
| #define BSD_CTRL_RAM_SIZE 4 /* bytes per pixel row */ |
| /* tile border coefficients of filter */ |
| #define VERT_SAO_RAM_SIZE 48 /* bytes per pixel */ |
| |
| #define SCALING_LIST_SIZE (16 * 64) |
| |
| #define MAX_TILE_COLS 20 |
| #define MAX_TILE_ROWS 22 |
| |
| static bool hevc_use_compression = IS_ENABLED(CONFIG_VIDEO_HANTRO_HEVC_RFC); |
| module_param_named(hevc_use_compression, hevc_use_compression, bool, 0644); |
| MODULE_PARM_DESC(hevc_use_compression, |
| "Use reference frame compression for HEVC"); |
| |
| void hantro_hevc_ref_init(struct hantro_ctx *ctx) |
| { |
| struct hantro_hevc_dec_hw_ctx *hevc_dec = &ctx->hevc_dec; |
| |
| hevc_dec->ref_bufs_used = 0; |
| } |
| |
| dma_addr_t hantro_hevc_get_ref_buf(struct hantro_ctx *ctx, |
| s32 poc) |
| { |
| struct hantro_hevc_dec_hw_ctx *hevc_dec = &ctx->hevc_dec; |
| int i; |
| |
| /* Find the reference buffer in already known ones */ |
| for (i = 0; i < NUM_REF_PICTURES; i++) { |
| if (hevc_dec->ref_bufs_poc[i] == poc) { |
| hevc_dec->ref_bufs_used |= 1 << i; |
| return hevc_dec->ref_bufs[i].dma; |
| } |
| } |
| |
| return 0; |
| } |
| |
| int hantro_hevc_add_ref_buf(struct hantro_ctx *ctx, int poc, dma_addr_t addr) |
| { |
| struct hantro_hevc_dec_hw_ctx *hevc_dec = &ctx->hevc_dec; |
| int i; |
| |
| /* Add a new reference buffer */ |
| for (i = 0; i < NUM_REF_PICTURES; i++) { |
| if (!(hevc_dec->ref_bufs_used & 1 << i)) { |
| hevc_dec->ref_bufs_used |= 1 << i; |
| hevc_dec->ref_bufs_poc[i] = poc; |
| hevc_dec->ref_bufs[i].dma = addr; |
| return 0; |
| } |
| } |
| |
| return -EINVAL; |
| } |
| |
| static int tile_buffer_reallocate(struct hantro_ctx *ctx) |
| { |
| struct hantro_dev *vpu = ctx->dev; |
| struct hantro_hevc_dec_hw_ctx *hevc_dec = &ctx->hevc_dec; |
| const struct hantro_hevc_dec_ctrls *ctrls = &ctx->hevc_dec.ctrls; |
| const struct v4l2_ctrl_hevc_pps *pps = ctrls->pps; |
| const struct v4l2_ctrl_hevc_sps *sps = ctrls->sps; |
| unsigned int num_tile_cols = pps->num_tile_columns_minus1 + 1; |
| unsigned int height64 = (sps->pic_height_in_luma_samples + 63) & ~63; |
| unsigned int size; |
| |
| if (num_tile_cols <= 1 || |
| num_tile_cols <= hevc_dec->num_tile_cols_allocated) |
| return 0; |
| |
| /* Need to reallocate due to tiles passed via PPS */ |
| if (hevc_dec->tile_filter.cpu) { |
| dma_free_coherent(vpu->dev, hevc_dec->tile_filter.size, |
| hevc_dec->tile_filter.cpu, |
| hevc_dec->tile_filter.dma); |
| hevc_dec->tile_filter.cpu = NULL; |
| } |
| |
| if (hevc_dec->tile_sao.cpu) { |
| dma_free_coherent(vpu->dev, hevc_dec->tile_sao.size, |
| hevc_dec->tile_sao.cpu, |
| hevc_dec->tile_sao.dma); |
| hevc_dec->tile_sao.cpu = NULL; |
| } |
| |
| if (hevc_dec->tile_bsd.cpu) { |
| dma_free_coherent(vpu->dev, hevc_dec->tile_bsd.size, |
| hevc_dec->tile_bsd.cpu, |
| hevc_dec->tile_bsd.dma); |
| hevc_dec->tile_bsd.cpu = NULL; |
| } |
| |
| size = (VERT_FILTER_RAM_SIZE * height64 * (num_tile_cols - 1) * ctx->bit_depth) / 8; |
| hevc_dec->tile_filter.cpu = dma_alloc_coherent(vpu->dev, size, |
| &hevc_dec->tile_filter.dma, |
| GFP_KERNEL); |
| if (!hevc_dec->tile_filter.cpu) |
| return -ENOMEM; |
| hevc_dec->tile_filter.size = size; |
| |
| size = (VERT_SAO_RAM_SIZE * height64 * (num_tile_cols - 1) * ctx->bit_depth) / 8; |
| hevc_dec->tile_sao.cpu = dma_alloc_coherent(vpu->dev, size, |
| &hevc_dec->tile_sao.dma, |
| GFP_KERNEL); |
| if (!hevc_dec->tile_sao.cpu) |
| goto err_free_tile_buffers; |
| hevc_dec->tile_sao.size = size; |
| |
| size = BSD_CTRL_RAM_SIZE * height64 * (num_tile_cols - 1); |
| hevc_dec->tile_bsd.cpu = dma_alloc_coherent(vpu->dev, size, |
| &hevc_dec->tile_bsd.dma, |
| GFP_KERNEL); |
| if (!hevc_dec->tile_bsd.cpu) |
| goto err_free_sao_buffers; |
| hevc_dec->tile_bsd.size = size; |
| |
| hevc_dec->num_tile_cols_allocated = num_tile_cols; |
| |
| return 0; |
| |
| err_free_sao_buffers: |
| if (hevc_dec->tile_sao.cpu) |
| dma_free_coherent(vpu->dev, hevc_dec->tile_sao.size, |
| hevc_dec->tile_sao.cpu, |
| hevc_dec->tile_sao.dma); |
| hevc_dec->tile_sao.cpu = NULL; |
| |
| err_free_tile_buffers: |
| if (hevc_dec->tile_filter.cpu) |
| dma_free_coherent(vpu->dev, hevc_dec->tile_filter.size, |
| hevc_dec->tile_filter.cpu, |
| hevc_dec->tile_filter.dma); |
| hevc_dec->tile_filter.cpu = NULL; |
| |
| return -ENOMEM; |
| } |
| |
| static int hantro_hevc_validate_sps(struct hantro_ctx *ctx, const struct v4l2_ctrl_hevc_sps *sps) |
| { |
| /* |
| * for tile pixel format check if the width and height match |
| * hardware constraints |
| */ |
| if (ctx->vpu_dst_fmt->fourcc == V4L2_PIX_FMT_NV12_4L4) { |
| if (ctx->dst_fmt.width != |
| ALIGN(sps->pic_width_in_luma_samples, ctx->vpu_dst_fmt->frmsize.step_width)) |
| return -EINVAL; |
| |
| if (ctx->dst_fmt.height != |
| ALIGN(sps->pic_height_in_luma_samples, ctx->vpu_dst_fmt->frmsize.step_height)) |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| int hantro_hevc_dec_prepare_run(struct hantro_ctx *ctx) |
| { |
| struct hantro_hevc_dec_hw_ctx *hevc_ctx = &ctx->hevc_dec; |
| struct hantro_hevc_dec_ctrls *ctrls = &hevc_ctx->ctrls; |
| int ret; |
| |
| hantro_start_prepare_run(ctx); |
| |
| ctrls->decode_params = |
| hantro_get_ctrl(ctx, V4L2_CID_STATELESS_HEVC_DECODE_PARAMS); |
| if (WARN_ON(!ctrls->decode_params)) |
| return -EINVAL; |
| |
| ctrls->scaling = |
| hantro_get_ctrl(ctx, V4L2_CID_STATELESS_HEVC_SCALING_MATRIX); |
| if (WARN_ON(!ctrls->scaling)) |
| return -EINVAL; |
| |
| ctrls->sps = |
| hantro_get_ctrl(ctx, V4L2_CID_STATELESS_HEVC_SPS); |
| if (WARN_ON(!ctrls->sps)) |
| return -EINVAL; |
| |
| ret = hantro_hevc_validate_sps(ctx, ctrls->sps); |
| if (ret) |
| return ret; |
| |
| ctrls->pps = |
| hantro_get_ctrl(ctx, V4L2_CID_STATELESS_HEVC_PPS); |
| if (WARN_ON(!ctrls->pps)) |
| return -EINVAL; |
| |
| ret = tile_buffer_reallocate(ctx); |
| if (ret) |
| return ret; |
| |
| return 0; |
| } |
| |
| void hantro_hevc_dec_exit(struct hantro_ctx *ctx) |
| { |
| struct hantro_dev *vpu = ctx->dev; |
| struct hantro_hevc_dec_hw_ctx *hevc_dec = &ctx->hevc_dec; |
| |
| if (hevc_dec->tile_sizes.cpu) |
| dma_free_coherent(vpu->dev, hevc_dec->tile_sizes.size, |
| hevc_dec->tile_sizes.cpu, |
| hevc_dec->tile_sizes.dma); |
| hevc_dec->tile_sizes.cpu = NULL; |
| |
| if (hevc_dec->scaling_lists.cpu) |
| dma_free_coherent(vpu->dev, hevc_dec->scaling_lists.size, |
| hevc_dec->scaling_lists.cpu, |
| hevc_dec->scaling_lists.dma); |
| hevc_dec->scaling_lists.cpu = NULL; |
| |
| if (hevc_dec->tile_filter.cpu) |
| dma_free_coherent(vpu->dev, hevc_dec->tile_filter.size, |
| hevc_dec->tile_filter.cpu, |
| hevc_dec->tile_filter.dma); |
| hevc_dec->tile_filter.cpu = NULL; |
| |
| if (hevc_dec->tile_sao.cpu) |
| dma_free_coherent(vpu->dev, hevc_dec->tile_sao.size, |
| hevc_dec->tile_sao.cpu, |
| hevc_dec->tile_sao.dma); |
| hevc_dec->tile_sao.cpu = NULL; |
| |
| if (hevc_dec->tile_bsd.cpu) |
| dma_free_coherent(vpu->dev, hevc_dec->tile_bsd.size, |
| hevc_dec->tile_bsd.cpu, |
| hevc_dec->tile_bsd.dma); |
| hevc_dec->tile_bsd.cpu = NULL; |
| } |
| |
| int hantro_hevc_dec_init(struct hantro_ctx *ctx) |
| { |
| struct hantro_dev *vpu = ctx->dev; |
| struct hantro_hevc_dec_hw_ctx *hevc_dec = &ctx->hevc_dec; |
| unsigned int size; |
| |
| memset(hevc_dec, 0, sizeof(*hevc_dec)); |
| |
| /* |
| * Maximum number of tiles times width and height (2 bytes each), |
| * rounding up to next 16 bytes boundary + one extra 16 byte |
| * chunk (HW guys wanted to have this). |
| */ |
| size = round_up(MAX_TILE_COLS * MAX_TILE_ROWS * 4 * sizeof(u16) + 16, 16); |
| hevc_dec->tile_sizes.cpu = dma_alloc_coherent(vpu->dev, size, |
| &hevc_dec->tile_sizes.dma, |
| GFP_KERNEL); |
| if (!hevc_dec->tile_sizes.cpu) |
| return -ENOMEM; |
| |
| hevc_dec->tile_sizes.size = size; |
| |
| hevc_dec->scaling_lists.cpu = dma_alloc_coherent(vpu->dev, SCALING_LIST_SIZE, |
| &hevc_dec->scaling_lists.dma, |
| GFP_KERNEL); |
| if (!hevc_dec->scaling_lists.cpu) |
| return -ENOMEM; |
| |
| hevc_dec->scaling_lists.size = SCALING_LIST_SIZE; |
| |
| hantro_hevc_ref_init(ctx); |
| |
| hevc_dec->use_compression = |
| hevc_use_compression & hantro_needs_postproc(ctx, ctx->vpu_dst_fmt); |
| |
| return 0; |
| } |