linux_dsm_epyc7002/drivers/gpu/ipu-v3/ipu-image-convert.c
Philipp Zabel fbefb84dd1 gpu: ipu-v3: image-convert: move tile burst alignment out of loop
Burst aligned input and output width can be calculated once per column,
instead of repeatedly for each tile in the column. The same goes for
input and output height per row. Also don't round up the same values
repeatedly.

Signed-off-by: Philipp Zabel <p.zabel@pengutronix.de>
2019-08-19 16:25:30 +02:00

2476 lines
68 KiB
C

// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2012-2016 Mentor Graphics Inc.
*
* Queued image conversion support, with tiling and rotation.
*/
#include <linux/interrupt.h>
#include <linux/dma-mapping.h>
#include <video/imx-ipu-image-convert.h>
#include "ipu-prv.h"
/*
* The IC Resizer has a restriction that the output frame from the
* resizer must be 1024 or less in both width (pixels) and height
* (lines).
*
* The image converter attempts to split up a conversion when
* the desired output (converted) frame resolution exceeds the
* IC resizer limit of 1024 in either dimension.
*
* If either dimension of the output frame exceeds the limit, the
* dimension is split into 1, 2, or 4 equal stripes, for a maximum
* of 4*4 or 16 tiles. A conversion is then carried out for each
* tile (but taking care to pass the full frame stride length to
* the DMA channel's parameter memory!). IDMA double-buffering is used
* to convert each tile back-to-back when possible (see note below
* when double_buffering boolean is set).
*
* Note that the input frame must be split up into the same number
* of tiles as the output frame:
*
* +---------+-----+
* +-----+---+ | A | B |
* | A | B | | | |
* +-----+---+ --> +---------+-----+
* | C | D | | C | D |
* +-----+---+ | | |
* +---------+-----+
*
* Clockwise 90° rotations are handled by first rescaling into a
* reusable temporary tile buffer and then rotating with the 8x8
* block rotator, writing to the correct destination:
*
* +-----+-----+
* | | |
* +-----+---+ +---------+ | C | A |
* | A | B | | A,B, | | | | |
* +-----+---+ --> | C,D | | --> | | |
* | C | D | +---------+ +-----+-----+
* +-----+---+ | D | B |
* | | |
* +-----+-----+
*
* If the 8x8 block rotator is used, horizontal or vertical flipping
* is done during the rotation step, otherwise flipping is done
* during the scaling step.
* With rotation or flipping, tile order changes between input and
* output image. Tiles are numbered row major from top left to bottom
* right for both input and output image.
*/
#define MAX_STRIPES_W 4
#define MAX_STRIPES_H 4
#define MAX_TILES (MAX_STRIPES_W * MAX_STRIPES_H)
#define MIN_W 16
#define MIN_H 8
#define MAX_W 4096
#define MAX_H 4096
enum ipu_image_convert_type {
IMAGE_CONVERT_IN = 0,
IMAGE_CONVERT_OUT,
};
struct ipu_image_convert_dma_buf {
void *virt;
dma_addr_t phys;
unsigned long len;
};
struct ipu_image_convert_dma_chan {
int in;
int out;
int rot_in;
int rot_out;
int vdi_in_p;
int vdi_in;
int vdi_in_n;
};
/* dimensions of one tile */
struct ipu_image_tile {
u32 width;
u32 height;
u32 left;
u32 top;
/* size and strides are in bytes */
u32 size;
u32 stride;
u32 rot_stride;
/* start Y or packed offset of this tile */
u32 offset;
/* offset from start to tile in U plane, for planar formats */
u32 u_off;
/* offset from start to tile in V plane, for planar formats */
u32 v_off;
};
struct ipu_image_convert_image {
struct ipu_image base;
enum ipu_image_convert_type type;
const struct ipu_image_pixfmt *fmt;
unsigned int stride;
/* # of rows (horizontal stripes) if dest height is > 1024 */
unsigned int num_rows;
/* # of columns (vertical stripes) if dest width is > 1024 */
unsigned int num_cols;
struct ipu_image_tile tile[MAX_TILES];
};
struct ipu_image_pixfmt {
u32 fourcc; /* V4L2 fourcc */
int bpp; /* total bpp */
int uv_width_dec; /* decimation in width for U/V planes */
int uv_height_dec; /* decimation in height for U/V planes */
bool planar; /* planar format */
bool uv_swapped; /* U and V planes are swapped */
bool uv_packed; /* partial planar (U and V in same plane) */
};
struct ipu_image_convert_ctx;
struct ipu_image_convert_chan;
struct ipu_image_convert_priv;
struct ipu_image_convert_ctx {
struct ipu_image_convert_chan *chan;
ipu_image_convert_cb_t complete;
void *complete_context;
/* Source/destination image data and rotation mode */
struct ipu_image_convert_image in;
struct ipu_image_convert_image out;
struct ipu_ic_csc csc;
enum ipu_rotate_mode rot_mode;
u32 downsize_coeff_h;
u32 downsize_coeff_v;
u32 image_resize_coeff_h;
u32 image_resize_coeff_v;
u32 resize_coeffs_h[MAX_STRIPES_W];
u32 resize_coeffs_v[MAX_STRIPES_H];
/* intermediate buffer for rotation */
struct ipu_image_convert_dma_buf rot_intermediate[2];
/* current buffer number for double buffering */
int cur_buf_num;
bool aborting;
struct completion aborted;
/* can we use double-buffering for this conversion operation? */
bool double_buffering;
/* num_rows * num_cols */
unsigned int num_tiles;
/* next tile to process */
unsigned int next_tile;
/* where to place converted tile in dest image */
unsigned int out_tile_map[MAX_TILES];
struct list_head list;
};
struct ipu_image_convert_chan {
struct ipu_image_convert_priv *priv;
enum ipu_ic_task ic_task;
const struct ipu_image_convert_dma_chan *dma_ch;
struct ipu_ic *ic;
struct ipuv3_channel *in_chan;
struct ipuv3_channel *out_chan;
struct ipuv3_channel *rotation_in_chan;
struct ipuv3_channel *rotation_out_chan;
/* the IPU end-of-frame irqs */
int out_eof_irq;
int rot_out_eof_irq;
spinlock_t irqlock;
/* list of convert contexts */
struct list_head ctx_list;
/* queue of conversion runs */
struct list_head pending_q;
/* queue of completed runs */
struct list_head done_q;
/* the current conversion run */
struct ipu_image_convert_run *current_run;
};
struct ipu_image_convert_priv {
struct ipu_image_convert_chan chan[IC_NUM_TASKS];
struct ipu_soc *ipu;
};
static const struct ipu_image_convert_dma_chan
image_convert_dma_chan[IC_NUM_TASKS] = {
[IC_TASK_VIEWFINDER] = {
.in = IPUV3_CHANNEL_MEM_IC_PRP_VF,
.out = IPUV3_CHANNEL_IC_PRP_VF_MEM,
.rot_in = IPUV3_CHANNEL_MEM_ROT_VF,
.rot_out = IPUV3_CHANNEL_ROT_VF_MEM,
.vdi_in_p = IPUV3_CHANNEL_MEM_VDI_PREV,
.vdi_in = IPUV3_CHANNEL_MEM_VDI_CUR,
.vdi_in_n = IPUV3_CHANNEL_MEM_VDI_NEXT,
},
[IC_TASK_POST_PROCESSOR] = {
.in = IPUV3_CHANNEL_MEM_IC_PP,
.out = IPUV3_CHANNEL_IC_PP_MEM,
.rot_in = IPUV3_CHANNEL_MEM_ROT_PP,
.rot_out = IPUV3_CHANNEL_ROT_PP_MEM,
},
};
static const struct ipu_image_pixfmt image_convert_formats[] = {
{
.fourcc = V4L2_PIX_FMT_RGB565,
.bpp = 16,
}, {
.fourcc = V4L2_PIX_FMT_RGB24,
.bpp = 24,
}, {
.fourcc = V4L2_PIX_FMT_BGR24,
.bpp = 24,
}, {
.fourcc = V4L2_PIX_FMT_RGB32,
.bpp = 32,
}, {
.fourcc = V4L2_PIX_FMT_BGR32,
.bpp = 32,
}, {
.fourcc = V4L2_PIX_FMT_XRGB32,
.bpp = 32,
}, {
.fourcc = V4L2_PIX_FMT_XBGR32,
.bpp = 32,
}, {
.fourcc = V4L2_PIX_FMT_BGRX32,
.bpp = 32,
}, {
.fourcc = V4L2_PIX_FMT_RGBX32,
.bpp = 32,
}, {
.fourcc = V4L2_PIX_FMT_YUYV,
.bpp = 16,
.uv_width_dec = 2,
.uv_height_dec = 1,
}, {
.fourcc = V4L2_PIX_FMT_UYVY,
.bpp = 16,
.uv_width_dec = 2,
.uv_height_dec = 1,
}, {
.fourcc = V4L2_PIX_FMT_YUV420,
.bpp = 12,
.planar = true,
.uv_width_dec = 2,
.uv_height_dec = 2,
}, {
.fourcc = V4L2_PIX_FMT_YVU420,
.bpp = 12,
.planar = true,
.uv_width_dec = 2,
.uv_height_dec = 2,
.uv_swapped = true,
}, {
.fourcc = V4L2_PIX_FMT_NV12,
.bpp = 12,
.planar = true,
.uv_width_dec = 2,
.uv_height_dec = 2,
.uv_packed = true,
}, {
.fourcc = V4L2_PIX_FMT_YUV422P,
.bpp = 16,
.planar = true,
.uv_width_dec = 2,
.uv_height_dec = 1,
}, {
.fourcc = V4L2_PIX_FMT_NV16,
.bpp = 16,
.planar = true,
.uv_width_dec = 2,
.uv_height_dec = 1,
.uv_packed = true,
},
};
static const struct ipu_image_pixfmt *get_format(u32 fourcc)
{
const struct ipu_image_pixfmt *ret = NULL;
unsigned int i;
for (i = 0; i < ARRAY_SIZE(image_convert_formats); i++) {
if (image_convert_formats[i].fourcc == fourcc) {
ret = &image_convert_formats[i];
break;
}
}
return ret;
}
static void dump_format(struct ipu_image_convert_ctx *ctx,
struct ipu_image_convert_image *ic_image)
{
struct ipu_image_convert_chan *chan = ctx->chan;
struct ipu_image_convert_priv *priv = chan->priv;
dev_dbg(priv->ipu->dev,
"task %u: ctx %p: %s format: %dx%d (%dx%d tiles), %c%c%c%c\n",
chan->ic_task, ctx,
ic_image->type == IMAGE_CONVERT_OUT ? "Output" : "Input",
ic_image->base.pix.width, ic_image->base.pix.height,
ic_image->num_cols, ic_image->num_rows,
ic_image->fmt->fourcc & 0xff,
(ic_image->fmt->fourcc >> 8) & 0xff,
(ic_image->fmt->fourcc >> 16) & 0xff,
(ic_image->fmt->fourcc >> 24) & 0xff);
}
int ipu_image_convert_enum_format(int index, u32 *fourcc)
{
const struct ipu_image_pixfmt *fmt;
if (index >= (int)ARRAY_SIZE(image_convert_formats))
return -EINVAL;
/* Format found */
fmt = &image_convert_formats[index];
*fourcc = fmt->fourcc;
return 0;
}
EXPORT_SYMBOL_GPL(ipu_image_convert_enum_format);
static void free_dma_buf(struct ipu_image_convert_priv *priv,
struct ipu_image_convert_dma_buf *buf)
{
if (buf->virt)
dma_free_coherent(priv->ipu->dev,
buf->len, buf->virt, buf->phys);
buf->virt = NULL;
buf->phys = 0;
}
static int alloc_dma_buf(struct ipu_image_convert_priv *priv,
struct ipu_image_convert_dma_buf *buf,
int size)
{
buf->len = PAGE_ALIGN(size);
buf->virt = dma_alloc_coherent(priv->ipu->dev, buf->len, &buf->phys,
GFP_DMA | GFP_KERNEL);
if (!buf->virt) {
dev_err(priv->ipu->dev, "failed to alloc dma buffer\n");
return -ENOMEM;
}
return 0;
}
static inline int num_stripes(int dim)
{
return (dim - 1) / 1024 + 1;
}
/*
* Calculate downsizing coefficients, which are the same for all tiles,
* and initial bilinear resizing coefficients, which are used to find the
* best seam positions.
* Also determine the number of tiles necessary to guarantee that no tile
* is larger than 1024 pixels in either dimension at the output and between
* IC downsizing and main processing sections.
*/
static int calc_image_resize_coefficients(struct ipu_image_convert_ctx *ctx,
struct ipu_image *in,
struct ipu_image *out)
{
u32 downsized_width = in->rect.width;
u32 downsized_height = in->rect.height;
u32 downsize_coeff_v = 0;
u32 downsize_coeff_h = 0;
u32 resized_width = out->rect.width;
u32 resized_height = out->rect.height;
u32 resize_coeff_h;
u32 resize_coeff_v;
u32 cols;
u32 rows;
if (ipu_rot_mode_is_irt(ctx->rot_mode)) {
resized_width = out->rect.height;
resized_height = out->rect.width;
}
/* Do not let invalid input lead to an endless loop below */
if (WARN_ON(resized_width == 0 || resized_height == 0))
return -EINVAL;
while (downsized_width >= resized_width * 2) {
downsized_width >>= 1;
downsize_coeff_h++;
}
while (downsized_height >= resized_height * 2) {
downsized_height >>= 1;
downsize_coeff_v++;
}
/*
* Calculate the bilinear resizing coefficients that could be used if
* we were converting with a single tile. The bottom right output pixel
* should sample as close as possible to the bottom right input pixel
* out of the decimator, but not overshoot it:
*/
resize_coeff_h = 8192 * (downsized_width - 1) / (resized_width - 1);
resize_coeff_v = 8192 * (downsized_height - 1) / (resized_height - 1);
/*
* Both the output of the IC downsizing section before being passed to
* the IC main processing section and the final output of the IC main
* processing section must be <= 1024 pixels in both dimensions.
*/
cols = num_stripes(max_t(u32, downsized_width, resized_width));
rows = num_stripes(max_t(u32, downsized_height, resized_height));
dev_dbg(ctx->chan->priv->ipu->dev,
"%s: hscale: >>%u, *8192/%u vscale: >>%u, *8192/%u, %ux%u tiles\n",
__func__, downsize_coeff_h, resize_coeff_h, downsize_coeff_v,
resize_coeff_v, cols, rows);
if (downsize_coeff_h > 2 || downsize_coeff_v > 2 ||
resize_coeff_h > 0x3fff || resize_coeff_v > 0x3fff)
return -EINVAL;
ctx->downsize_coeff_h = downsize_coeff_h;
ctx->downsize_coeff_v = downsize_coeff_v;
ctx->image_resize_coeff_h = resize_coeff_h;
ctx->image_resize_coeff_v = resize_coeff_v;
ctx->in.num_cols = cols;
ctx->in.num_rows = rows;
return 0;
}
#define round_closest(x, y) round_down((x) + (y)/2, (y))
/*
* Find the best aligned seam position for the given column / row index.
* Rotation and image offsets are out of scope.
*
* @index: column / row index, used to calculate valid interval
* @in_edge: input right / bottom edge
* @out_edge: output right / bottom edge
* @in_align: input alignment, either horizontal 8-byte line start address
* alignment, or pixel alignment due to image format
* @out_align: output alignment, either horizontal 8-byte line start address
* alignment, or pixel alignment due to image format or rotator
* block size
* @in_burst: horizontal input burst size in case of horizontal flip
* @out_burst: horizontal output burst size or rotator block size
* @downsize_coeff: downsizing section coefficient
* @resize_coeff: main processing section resizing coefficient
* @_in_seam: aligned input seam position return value
* @_out_seam: aligned output seam position return value
*/
static void find_best_seam(struct ipu_image_convert_ctx *ctx,
unsigned int index,
unsigned int in_edge,
unsigned int out_edge,
unsigned int in_align,
unsigned int out_align,
unsigned int in_burst,
unsigned int out_burst,
unsigned int downsize_coeff,
unsigned int resize_coeff,
u32 *_in_seam,
u32 *_out_seam)
{
struct device *dev = ctx->chan->priv->ipu->dev;
unsigned int out_pos;
/* Input / output seam position candidates */
unsigned int out_seam = 0;
unsigned int in_seam = 0;
unsigned int min_diff = UINT_MAX;
unsigned int out_start;
unsigned int out_end;
unsigned int in_start;
unsigned int in_end;
/* Start within 1024 pixels of the right / bottom edge */
out_start = max_t(int, index * out_align, out_edge - 1024);
/* End before having to add more columns to the left / rows above */
out_end = min_t(unsigned int, out_edge, index * 1024 + 1);
/*
* Limit input seam position to make sure that the downsized input tile
* to the right or bottom does not exceed 1024 pixels.
*/
in_start = max_t(int, index * in_align,
in_edge - (1024 << downsize_coeff));
in_end = min_t(unsigned int, in_edge,
index * (1024 << downsize_coeff) + 1);
/*
* Output tiles must start at a multiple of 8 bytes horizontally and
* possibly at an even line horizontally depending on the pixel format.
* Only consider output aligned positions for the seam.
*/
out_start = round_up(out_start, out_align);
for (out_pos = out_start; out_pos < out_end; out_pos += out_align) {
unsigned int in_pos;
unsigned int in_pos_aligned;
unsigned int in_pos_rounded;
unsigned int abs_diff;
/*
* Tiles in the right row / bottom column may not be allowed to
* overshoot horizontally / vertically. out_burst may be the
* actual DMA burst size, or the rotator block size.
*/
if ((out_burst > 1) && (out_edge - out_pos) % out_burst)
continue;
/*
* Input sample position, corresponding to out_pos, 19.13 fixed
* point.
*/
in_pos = (out_pos * resize_coeff) << downsize_coeff;
/*
* The closest input sample position that we could actually
* start the input tile at, 19.13 fixed point.
*/
in_pos_aligned = round_closest(in_pos, 8192U * in_align);
/* Convert 19.13 fixed point to integer */
in_pos_rounded = in_pos_aligned / 8192U;
if (in_pos_rounded < in_start)
continue;
if (in_pos_rounded >= in_end)
break;
if ((in_burst > 1) &&
(in_edge - in_pos_rounded) % in_burst)
continue;
if (in_pos < in_pos_aligned)
abs_diff = in_pos_aligned - in_pos;
else
abs_diff = in_pos - in_pos_aligned;
if (abs_diff < min_diff) {
in_seam = in_pos_rounded;
out_seam = out_pos;
min_diff = abs_diff;
}
}
*_out_seam = out_seam;
*_in_seam = in_seam;
dev_dbg(dev, "%s: out_seam %u(%u) in [%u, %u], in_seam %u(%u) in [%u, %u] diff %u.%03u\n",
__func__, out_seam, out_align, out_start, out_end,
in_seam, in_align, in_start, in_end, min_diff / 8192,
DIV_ROUND_CLOSEST(min_diff % 8192 * 1000, 8192));
}
/*
* Tile left edges are required to be aligned to multiples of 8 bytes
* by the IDMAC.
*/
static inline u32 tile_left_align(const struct ipu_image_pixfmt *fmt)
{
if (fmt->planar)
return fmt->uv_packed ? 8 : 8 * fmt->uv_width_dec;
else
return fmt->bpp == 32 ? 2 : fmt->bpp == 16 ? 4 : 8;
}
/*
* Tile top edge alignment is only limited by chroma subsampling.
*/
static inline u32 tile_top_align(const struct ipu_image_pixfmt *fmt)
{
return fmt->uv_height_dec > 1 ? 2 : 1;
}
static inline u32 tile_width_align(enum ipu_image_convert_type type,
const struct ipu_image_pixfmt *fmt,
enum ipu_rotate_mode rot_mode)
{
if (type == IMAGE_CONVERT_IN) {
/*
* The IC burst reads 8 pixels at a time. Reading beyond the
* end of the line is usually acceptable. Those pixels are
* ignored, unless the IC has to write the scaled line in
* reverse.
*/
return (!ipu_rot_mode_is_irt(rot_mode) &&
(rot_mode & IPU_ROT_BIT_HFLIP)) ? 8 : 2;
}
/*
* Align to 16x16 pixel blocks for planar 4:2:0 chroma subsampled
* formats to guarantee 8-byte aligned line start addresses in the
* chroma planes when IRT is used. Align to 8x8 pixel IRT block size
* for all other formats.
*/
return (ipu_rot_mode_is_irt(rot_mode) &&
fmt->planar && !fmt->uv_packed) ?
8 * fmt->uv_width_dec : 8;
}
static inline u32 tile_height_align(enum ipu_image_convert_type type,
const struct ipu_image_pixfmt *fmt,
enum ipu_rotate_mode rot_mode)
{
if (type == IMAGE_CONVERT_IN || !ipu_rot_mode_is_irt(rot_mode))
return 2;
/*
* Align to 16x16 pixel blocks for planar 4:2:0 chroma subsampled
* formats to guarantee 8-byte aligned line start addresses in the
* chroma planes when IRT is used. Align to 8x8 pixel IRT block size
* for all other formats.
*/
return (fmt->planar && !fmt->uv_packed) ? 8 * fmt->uv_width_dec : 8;
}
/*
* Fill in left position and width and for all tiles in an input column, and
* for all corresponding output tiles. If the 90° rotator is used, the output
* tiles are in a row, and output tile top position and height are set.
*/
static void fill_tile_column(struct ipu_image_convert_ctx *ctx,
unsigned int col,
struct ipu_image_convert_image *in,
unsigned int in_left, unsigned int in_width,
struct ipu_image_convert_image *out,
unsigned int out_left, unsigned int out_width)
{
unsigned int row, tile_idx;
struct ipu_image_tile *in_tile, *out_tile;
for (row = 0; row < in->num_rows; row++) {
tile_idx = in->num_cols * row + col;
in_tile = &in->tile[tile_idx];
out_tile = &out->tile[ctx->out_tile_map[tile_idx]];
in_tile->left = in_left;
in_tile->width = in_width;
if (ipu_rot_mode_is_irt(ctx->rot_mode)) {
out_tile->top = out_left;
out_tile->height = out_width;
} else {
out_tile->left = out_left;
out_tile->width = out_width;
}
}
}
/*
* Fill in top position and height and for all tiles in an input row, and
* for all corresponding output tiles. If the 90° rotator is used, the output
* tiles are in a column, and output tile left position and width are set.
*/
static void fill_tile_row(struct ipu_image_convert_ctx *ctx, unsigned int row,
struct ipu_image_convert_image *in,
unsigned int in_top, unsigned int in_height,
struct ipu_image_convert_image *out,
unsigned int out_top, unsigned int out_height)
{
unsigned int col, tile_idx;
struct ipu_image_tile *in_tile, *out_tile;
for (col = 0; col < in->num_cols; col++) {
tile_idx = in->num_cols * row + col;
in_tile = &in->tile[tile_idx];
out_tile = &out->tile[ctx->out_tile_map[tile_idx]];
in_tile->top = in_top;
in_tile->height = in_height;
if (ipu_rot_mode_is_irt(ctx->rot_mode)) {
out_tile->left = out_top;
out_tile->width = out_height;
} else {
out_tile->top = out_top;
out_tile->height = out_height;
}
}
}
/*
* Find the best horizontal and vertical seam positions to split into tiles.
* Minimize the fractional part of the input sampling position for the
* top / left pixels of each tile.
*/
static void find_seams(struct ipu_image_convert_ctx *ctx,
struct ipu_image_convert_image *in,
struct ipu_image_convert_image *out)
{
struct device *dev = ctx->chan->priv->ipu->dev;
unsigned int resized_width = out->base.rect.width;
unsigned int resized_height = out->base.rect.height;
unsigned int col;
unsigned int row;
unsigned int in_left_align = tile_left_align(in->fmt);
unsigned int in_top_align = tile_top_align(in->fmt);
unsigned int out_left_align = tile_left_align(out->fmt);
unsigned int out_top_align = tile_top_align(out->fmt);
unsigned int out_width_align = tile_width_align(out->type, out->fmt,
ctx->rot_mode);
unsigned int out_height_align = tile_height_align(out->type, out->fmt,
ctx->rot_mode);
unsigned int in_right = in->base.rect.width;
unsigned int in_bottom = in->base.rect.height;
unsigned int out_right = out->base.rect.width;
unsigned int out_bottom = out->base.rect.height;
unsigned int flipped_out_left;
unsigned int flipped_out_top;
if (ipu_rot_mode_is_irt(ctx->rot_mode)) {
/* Switch width/height and align top left to IRT block size */
resized_width = out->base.rect.height;
resized_height = out->base.rect.width;
out_left_align = out_height_align;
out_top_align = out_width_align;
out_width_align = out_left_align;
out_height_align = out_top_align;
out_right = out->base.rect.height;
out_bottom = out->base.rect.width;
}
for (col = in->num_cols - 1; col > 0; col--) {
bool allow_in_overshoot = ipu_rot_mode_is_irt(ctx->rot_mode) ||
!(ctx->rot_mode & IPU_ROT_BIT_HFLIP);
bool allow_out_overshoot = (col < in->num_cols - 1) &&
!(ctx->rot_mode & IPU_ROT_BIT_HFLIP);
unsigned int in_left;
unsigned int out_left;
/*
* Align input width to burst length if the scaling step flips
* horizontally.
*/
find_best_seam(ctx, col,
in_right, out_right,
in_left_align, out_left_align,
allow_in_overshoot ? 1 : 8 /* burst length */,
allow_out_overshoot ? 1 : out_width_align,
ctx->downsize_coeff_h, ctx->image_resize_coeff_h,
&in_left, &out_left);
if (ctx->rot_mode & IPU_ROT_BIT_HFLIP)
flipped_out_left = resized_width - out_right;
else
flipped_out_left = out_left;
fill_tile_column(ctx, col, in, in_left, in_right - in_left,
out, flipped_out_left, out_right - out_left);
dev_dbg(dev, "%s: col %u: %u, %u -> %u, %u\n", __func__, col,
in_left, in_right - in_left,
flipped_out_left, out_right - out_left);
in_right = in_left;
out_right = out_left;
}
flipped_out_left = (ctx->rot_mode & IPU_ROT_BIT_HFLIP) ?
resized_width - out_right : 0;
fill_tile_column(ctx, 0, in, 0, in_right,
out, flipped_out_left, out_right);
dev_dbg(dev, "%s: col 0: 0, %u -> %u, %u\n", __func__,
in_right, flipped_out_left, out_right);
for (row = in->num_rows - 1; row > 0; row--) {
bool allow_overshoot = row < in->num_rows - 1;
unsigned int in_top;
unsigned int out_top;
find_best_seam(ctx, row,
in_bottom, out_bottom,
in_top_align, out_top_align,
1, allow_overshoot ? 1 : out_height_align,
ctx->downsize_coeff_v, ctx->image_resize_coeff_v,
&in_top, &out_top);
if ((ctx->rot_mode & IPU_ROT_BIT_VFLIP) ^
ipu_rot_mode_is_irt(ctx->rot_mode))
flipped_out_top = resized_height - out_bottom;
else
flipped_out_top = out_top;
fill_tile_row(ctx, row, in, in_top, in_bottom - in_top,
out, flipped_out_top, out_bottom - out_top);
dev_dbg(dev, "%s: row %u: %u, %u -> %u, %u\n", __func__, row,
in_top, in_bottom - in_top,
flipped_out_top, out_bottom - out_top);
in_bottom = in_top;
out_bottom = out_top;
}
if ((ctx->rot_mode & IPU_ROT_BIT_VFLIP) ^
ipu_rot_mode_is_irt(ctx->rot_mode))
flipped_out_top = resized_height - out_bottom;
else
flipped_out_top = 0;
fill_tile_row(ctx, 0, in, 0, in_bottom,
out, flipped_out_top, out_bottom);
dev_dbg(dev, "%s: row 0: 0, %u -> %u, %u\n", __func__,
in_bottom, flipped_out_top, out_bottom);
}
static int calc_tile_dimensions(struct ipu_image_convert_ctx *ctx,
struct ipu_image_convert_image *image)
{
struct ipu_image_convert_chan *chan = ctx->chan;
struct ipu_image_convert_priv *priv = chan->priv;
unsigned int max_width = 1024;
unsigned int max_height = 1024;
unsigned int i;
if (image->type == IMAGE_CONVERT_IN) {
/* Up to 4096x4096 input tile size */
max_width <<= ctx->downsize_coeff_h;
max_height <<= ctx->downsize_coeff_v;
}
for (i = 0; i < ctx->num_tiles; i++) {
struct ipu_image_tile *tile;
const unsigned int row = i / image->num_cols;
const unsigned int col = i % image->num_cols;
if (image->type == IMAGE_CONVERT_OUT)
tile = &image->tile[ctx->out_tile_map[i]];
else
tile = &image->tile[i];
tile->size = ((tile->height * image->fmt->bpp) >> 3) *
tile->width;
if (image->fmt->planar) {
tile->stride = tile->width;
tile->rot_stride = tile->height;
} else {
tile->stride =
(image->fmt->bpp * tile->width) >> 3;
tile->rot_stride =
(image->fmt->bpp * tile->height) >> 3;
}
dev_dbg(priv->ipu->dev,
"task %u: ctx %p: %s@[%u,%u]: %ux%u@%u,%u\n",
chan->ic_task, ctx,
image->type == IMAGE_CONVERT_IN ? "Input" : "Output",
row, col,
tile->width, tile->height, tile->left, tile->top);
if (!tile->width || tile->width > max_width ||
!tile->height || tile->height > max_height) {
dev_err(priv->ipu->dev, "invalid %s tile size: %ux%u\n",
image->type == IMAGE_CONVERT_IN ? "input" :
"output", tile->width, tile->height);
return -EINVAL;
}
}
return 0;
}
/*
* Use the rotation transformation to find the tile coordinates
* (row, col) of a tile in the destination frame that corresponds
* to the given tile coordinates of a source frame. The destination
* coordinate is then converted to a tile index.
*/
static int transform_tile_index(struct ipu_image_convert_ctx *ctx,
int src_row, int src_col)
{
struct ipu_image_convert_chan *chan = ctx->chan;
struct ipu_image_convert_priv *priv = chan->priv;
struct ipu_image_convert_image *s_image = &ctx->in;
struct ipu_image_convert_image *d_image = &ctx->out;
int dst_row, dst_col;
/* with no rotation it's a 1:1 mapping */
if (ctx->rot_mode == IPU_ROTATE_NONE)
return src_row * s_image->num_cols + src_col;
/*
* before doing the transform, first we have to translate
* source row,col for an origin in the center of s_image
*/
src_row = src_row * 2 - (s_image->num_rows - 1);
src_col = src_col * 2 - (s_image->num_cols - 1);
/* do the rotation transform */
if (ctx->rot_mode & IPU_ROT_BIT_90) {
dst_col = -src_row;
dst_row = src_col;
} else {
dst_col = src_col;
dst_row = src_row;
}
/* apply flip */
if (ctx->rot_mode & IPU_ROT_BIT_HFLIP)
dst_col = -dst_col;
if (ctx->rot_mode & IPU_ROT_BIT_VFLIP)
dst_row = -dst_row;
dev_dbg(priv->ipu->dev, "task %u: ctx %p: [%d,%d] --> [%d,%d]\n",
chan->ic_task, ctx, src_col, src_row, dst_col, dst_row);
/*
* finally translate dest row,col using an origin in upper
* left of d_image
*/
dst_row += d_image->num_rows - 1;
dst_col += d_image->num_cols - 1;
dst_row /= 2;
dst_col /= 2;
return dst_row * d_image->num_cols + dst_col;
}
/*
* Fill the out_tile_map[] with transformed destination tile indeces.
*/
static void calc_out_tile_map(struct ipu_image_convert_ctx *ctx)
{
struct ipu_image_convert_image *s_image = &ctx->in;
unsigned int row, col, tile = 0;
for (row = 0; row < s_image->num_rows; row++) {
for (col = 0; col < s_image->num_cols; col++) {
ctx->out_tile_map[tile] =
transform_tile_index(ctx, row, col);
tile++;
}
}
}
static int calc_tile_offsets_planar(struct ipu_image_convert_ctx *ctx,
struct ipu_image_convert_image *image)
{
struct ipu_image_convert_chan *chan = ctx->chan;
struct ipu_image_convert_priv *priv = chan->priv;
const struct ipu_image_pixfmt *fmt = image->fmt;
unsigned int row, col, tile = 0;
u32 H, top, y_stride, uv_stride;
u32 uv_row_off, uv_col_off, uv_off, u_off, v_off, tmp;
u32 y_row_off, y_col_off, y_off;
u32 y_size, uv_size;
/* setup some convenience vars */
H = image->base.pix.height;
y_stride = image->stride;
uv_stride = y_stride / fmt->uv_width_dec;
if (fmt->uv_packed)
uv_stride *= 2;
y_size = H * y_stride;
uv_size = y_size / (fmt->uv_width_dec * fmt->uv_height_dec);
for (row = 0; row < image->num_rows; row++) {
top = image->tile[tile].top;
y_row_off = top * y_stride;
uv_row_off = (top * uv_stride) / fmt->uv_height_dec;
for (col = 0; col < image->num_cols; col++) {
y_col_off = image->tile[tile].left;
uv_col_off = y_col_off / fmt->uv_width_dec;
if (fmt->uv_packed)
uv_col_off *= 2;
y_off = y_row_off + y_col_off;
uv_off = uv_row_off + uv_col_off;
u_off = y_size - y_off + uv_off;
v_off = (fmt->uv_packed) ? 0 : u_off + uv_size;
if (fmt->uv_swapped) {
tmp = u_off;
u_off = v_off;
v_off = tmp;
}
image->tile[tile].offset = y_off;
image->tile[tile].u_off = u_off;
image->tile[tile++].v_off = v_off;
if ((y_off & 0x7) || (u_off & 0x7) || (v_off & 0x7)) {
dev_err(priv->ipu->dev,
"task %u: ctx %p: %s@[%d,%d]: "
"y_off %08x, u_off %08x, v_off %08x\n",
chan->ic_task, ctx,
image->type == IMAGE_CONVERT_IN ?
"Input" : "Output", row, col,
y_off, u_off, v_off);
return -EINVAL;
}
}
}
return 0;
}
static int calc_tile_offsets_packed(struct ipu_image_convert_ctx *ctx,
struct ipu_image_convert_image *image)
{
struct ipu_image_convert_chan *chan = ctx->chan;
struct ipu_image_convert_priv *priv = chan->priv;
const struct ipu_image_pixfmt *fmt = image->fmt;
unsigned int row, col, tile = 0;
u32 bpp, stride, offset;
u32 row_off, col_off;
/* setup some convenience vars */
stride = image->stride;
bpp = fmt->bpp;
for (row = 0; row < image->num_rows; row++) {
row_off = image->tile[tile].top * stride;
for (col = 0; col < image->num_cols; col++) {
col_off = (image->tile[tile].left * bpp) >> 3;
offset = row_off + col_off;
image->tile[tile].offset = offset;
image->tile[tile].u_off = 0;
image->tile[tile++].v_off = 0;
if (offset & 0x7) {
dev_err(priv->ipu->dev,
"task %u: ctx %p: %s@[%d,%d]: "
"phys %08x\n",
chan->ic_task, ctx,
image->type == IMAGE_CONVERT_IN ?
"Input" : "Output", row, col,
row_off + col_off);
return -EINVAL;
}
}
}
return 0;
}
static int calc_tile_offsets(struct ipu_image_convert_ctx *ctx,
struct ipu_image_convert_image *image)
{
if (image->fmt->planar)
return calc_tile_offsets_planar(ctx, image);
return calc_tile_offsets_packed(ctx, image);
}
/*
* Calculate the resizing ratio for the IC main processing section given input
* size, fixed downsizing coefficient, and output size.
* Either round to closest for the next tile's first pixel to minimize seams
* and distortion (for all but right column / bottom row), or round down to
* avoid sampling beyond the edges of the input image for this tile's last
* pixel.
* Returns the resizing coefficient, resizing ratio is 8192.0 / resize_coeff.
*/
static u32 calc_resize_coeff(u32 input_size, u32 downsize_coeff,
u32 output_size, bool allow_overshoot)
{
u32 downsized = input_size >> downsize_coeff;
if (allow_overshoot)
return DIV_ROUND_CLOSEST(8192 * downsized, output_size);
else
return 8192 * (downsized - 1) / (output_size - 1);
}
/*
* Slightly modify resize coefficients per tile to hide the bilinear
* interpolator reset at tile borders, shifting the right / bottom edge
* by up to a half input pixel. This removes noticeable seams between
* tiles at higher upscaling factors.
*/
static void calc_tile_resize_coefficients(struct ipu_image_convert_ctx *ctx)
{
struct ipu_image_convert_chan *chan = ctx->chan;
struct ipu_image_convert_priv *priv = chan->priv;
struct ipu_image_tile *in_tile, *out_tile;
unsigned int col, row, tile_idx;
unsigned int last_output;
for (col = 0; col < ctx->in.num_cols; col++) {
bool closest = (col < ctx->in.num_cols - 1) &&
!(ctx->rot_mode & IPU_ROT_BIT_HFLIP);
u32 resized_width;
u32 resize_coeff_h;
u32 in_width;
tile_idx = col;
in_tile = &ctx->in.tile[tile_idx];
out_tile = &ctx->out.tile[ctx->out_tile_map[tile_idx]];
if (ipu_rot_mode_is_irt(ctx->rot_mode))
resized_width = out_tile->height;
else
resized_width = out_tile->width;
resize_coeff_h = calc_resize_coeff(in_tile->width,
ctx->downsize_coeff_h,
resized_width, closest);
dev_dbg(priv->ipu->dev, "%s: column %u hscale: *8192/%u\n",
__func__, col, resize_coeff_h);
/*
* With the horizontal scaling factor known, round up resized
* width (output width or height) to burst size.
*/
resized_width = round_up(resized_width, 8);
/*
* Calculate input width from the last accessed input pixel
* given resized width and scaling coefficients. Round up to
* burst size.
*/
last_output = resized_width - 1;
if (closest)
last_output++;
in_width = round_up(
(DIV_ROUND_UP(last_output * resize_coeff_h, 8192) + 1)
<< ctx->downsize_coeff_h, 8);
for (row = 0; row < ctx->in.num_rows; row++) {
tile_idx = row * ctx->in.num_cols + col;
in_tile = &ctx->in.tile[tile_idx];
out_tile = &ctx->out.tile[ctx->out_tile_map[tile_idx]];
if (ipu_rot_mode_is_irt(ctx->rot_mode))
out_tile->height = resized_width;
else
out_tile->width = resized_width;
in_tile->width = in_width;
}
ctx->resize_coeffs_h[col] = resize_coeff_h;
}
for (row = 0; row < ctx->in.num_rows; row++) {
bool closest = (row < ctx->in.num_rows - 1) &&
!(ctx->rot_mode & IPU_ROT_BIT_VFLIP);
u32 resized_height;
u32 resize_coeff_v;
u32 in_height;
tile_idx = row * ctx->in.num_cols;
in_tile = &ctx->in.tile[tile_idx];
out_tile = &ctx->out.tile[ctx->out_tile_map[tile_idx]];
if (ipu_rot_mode_is_irt(ctx->rot_mode))
resized_height = out_tile->width;
else
resized_height = out_tile->height;
resize_coeff_v = calc_resize_coeff(in_tile->height,
ctx->downsize_coeff_v,
resized_height, closest);
dev_dbg(priv->ipu->dev, "%s: row %u vscale: *8192/%u\n",
__func__, row, resize_coeff_v);
/*
* With the vertical scaling factor known, round up resized
* height (output width or height) to IDMAC limitations.
*/
resized_height = round_up(resized_height, 2);
/*
* Calculate input width from the last accessed input pixel
* given resized height and scaling coefficients. Align to
* IDMAC restrictions.
*/
last_output = resized_height - 1;
if (closest)
last_output++;
in_height = round_up(
(DIV_ROUND_UP(last_output * resize_coeff_v, 8192) + 1)
<< ctx->downsize_coeff_v, 2);
for (col = 0; col < ctx->in.num_cols; col++) {
tile_idx = row * ctx->in.num_cols + col;
in_tile = &ctx->in.tile[tile_idx];
out_tile = &ctx->out.tile[ctx->out_tile_map[tile_idx]];
if (ipu_rot_mode_is_irt(ctx->rot_mode))
out_tile->width = resized_height;
else
out_tile->height = resized_height;
in_tile->height = in_height;
}
ctx->resize_coeffs_v[row] = resize_coeff_v;
}
}
/*
* return the number of runs in given queue (pending_q or done_q)
* for this context. hold irqlock when calling.
*/
static int get_run_count(struct ipu_image_convert_ctx *ctx,
struct list_head *q)
{
struct ipu_image_convert_run *run;
int count = 0;
lockdep_assert_held(&ctx->chan->irqlock);
list_for_each_entry(run, q, list) {
if (run->ctx == ctx)
count++;
}
return count;
}
static void convert_stop(struct ipu_image_convert_run *run)
{
struct ipu_image_convert_ctx *ctx = run->ctx;
struct ipu_image_convert_chan *chan = ctx->chan;
struct ipu_image_convert_priv *priv = chan->priv;
dev_dbg(priv->ipu->dev, "%s: task %u: stopping ctx %p run %p\n",
__func__, chan->ic_task, ctx, run);
/* disable IC tasks and the channels */
ipu_ic_task_disable(chan->ic);
ipu_idmac_disable_channel(chan->in_chan);
ipu_idmac_disable_channel(chan->out_chan);
if (ipu_rot_mode_is_irt(ctx->rot_mode)) {
ipu_idmac_disable_channel(chan->rotation_in_chan);
ipu_idmac_disable_channel(chan->rotation_out_chan);
ipu_idmac_unlink(chan->out_chan, chan->rotation_in_chan);
}
ipu_ic_disable(chan->ic);
}
static void init_idmac_channel(struct ipu_image_convert_ctx *ctx,
struct ipuv3_channel *channel,
struct ipu_image_convert_image *image,
enum ipu_rotate_mode rot_mode,
bool rot_swap_width_height,
unsigned int tile)
{
struct ipu_image_convert_chan *chan = ctx->chan;
unsigned int burst_size;
u32 width, height, stride;
dma_addr_t addr0, addr1 = 0;
struct ipu_image tile_image;
unsigned int tile_idx[2];
if (image->type == IMAGE_CONVERT_OUT) {
tile_idx[0] = ctx->out_tile_map[tile];
tile_idx[1] = ctx->out_tile_map[1];
} else {
tile_idx[0] = tile;
tile_idx[1] = 1;
}
if (rot_swap_width_height) {
width = image->tile[tile_idx[0]].height;
height = image->tile[tile_idx[0]].width;
stride = image->tile[tile_idx[0]].rot_stride;
addr0 = ctx->rot_intermediate[0].phys;
if (ctx->double_buffering)
addr1 = ctx->rot_intermediate[1].phys;
} else {
width = image->tile[tile_idx[0]].width;
height = image->tile[tile_idx[0]].height;
stride = image->stride;
addr0 = image->base.phys0 +
image->tile[tile_idx[0]].offset;
if (ctx->double_buffering)
addr1 = image->base.phys0 +
image->tile[tile_idx[1]].offset;
}
ipu_cpmem_zero(channel);
memset(&tile_image, 0, sizeof(tile_image));
tile_image.pix.width = tile_image.rect.width = width;
tile_image.pix.height = tile_image.rect.height = height;
tile_image.pix.bytesperline = stride;
tile_image.pix.pixelformat = image->fmt->fourcc;
tile_image.phys0 = addr0;
tile_image.phys1 = addr1;
if (image->fmt->planar && !rot_swap_width_height) {
tile_image.u_offset = image->tile[tile_idx[0]].u_off;
tile_image.v_offset = image->tile[tile_idx[0]].v_off;
}
ipu_cpmem_set_image(channel, &tile_image);
if (rot_mode)
ipu_cpmem_set_rotation(channel, rot_mode);
/*
* Skip writing U and V components to odd rows in the output
* channels for planar 4:2:0.
*/
if ((channel == chan->out_chan ||
channel == chan->rotation_out_chan) &&
image->fmt->planar && image->fmt->uv_height_dec == 2)
ipu_cpmem_skip_odd_chroma_rows(channel);
if (channel == chan->rotation_in_chan ||
channel == chan->rotation_out_chan) {
burst_size = 8;
ipu_cpmem_set_block_mode(channel);
} else
burst_size = (width % 16) ? 8 : 16;
ipu_cpmem_set_burstsize(channel, burst_size);
ipu_ic_task_idma_init(chan->ic, channel, width, height,
burst_size, rot_mode);
/*
* Setting a non-zero AXI ID collides with the PRG AXI snooping, so
* only do this when there is no PRG present.
*/
if (!channel->ipu->prg_priv)
ipu_cpmem_set_axi_id(channel, 1);
ipu_idmac_set_double_buffer(channel, ctx->double_buffering);
}
static int convert_start(struct ipu_image_convert_run *run, unsigned int tile)
{
struct ipu_image_convert_ctx *ctx = run->ctx;
struct ipu_image_convert_chan *chan = ctx->chan;
struct ipu_image_convert_priv *priv = chan->priv;
struct ipu_image_convert_image *s_image = &ctx->in;
struct ipu_image_convert_image *d_image = &ctx->out;
unsigned int dst_tile = ctx->out_tile_map[tile];
unsigned int dest_width, dest_height;
unsigned int col, row;
u32 rsc;
int ret;
dev_dbg(priv->ipu->dev, "%s: task %u: starting ctx %p run %p tile %u -> %u\n",
__func__, chan->ic_task, ctx, run, tile, dst_tile);
if (ipu_rot_mode_is_irt(ctx->rot_mode)) {
/* swap width/height for resizer */
dest_width = d_image->tile[dst_tile].height;
dest_height = d_image->tile[dst_tile].width;
} else {
dest_width = d_image->tile[dst_tile].width;
dest_height = d_image->tile[dst_tile].height;
}
row = tile / s_image->num_cols;
col = tile % s_image->num_cols;
rsc = (ctx->downsize_coeff_v << 30) |
(ctx->resize_coeffs_v[row] << 16) |
(ctx->downsize_coeff_h << 14) |
(ctx->resize_coeffs_h[col]);
dev_dbg(priv->ipu->dev, "%s: %ux%u -> %ux%u (rsc = 0x%x)\n",
__func__, s_image->tile[tile].width,
s_image->tile[tile].height, dest_width, dest_height, rsc);
/* setup the IC resizer and CSC */
ret = ipu_ic_task_init_rsc(chan->ic, &ctx->csc,
s_image->tile[tile].width,
s_image->tile[tile].height,
dest_width,
dest_height,
rsc);
if (ret) {
dev_err(priv->ipu->dev, "ipu_ic_task_init failed, %d\n", ret);
return ret;
}
/* init the source MEM-->IC PP IDMAC channel */
init_idmac_channel(ctx, chan->in_chan, s_image,
IPU_ROTATE_NONE, false, tile);
if (ipu_rot_mode_is_irt(ctx->rot_mode)) {
/* init the IC PP-->MEM IDMAC channel */
init_idmac_channel(ctx, chan->out_chan, d_image,
IPU_ROTATE_NONE, true, tile);
/* init the MEM-->IC PP ROT IDMAC channel */
init_idmac_channel(ctx, chan->rotation_in_chan, d_image,
ctx->rot_mode, true, tile);
/* init the destination IC PP ROT-->MEM IDMAC channel */
init_idmac_channel(ctx, chan->rotation_out_chan, d_image,
IPU_ROTATE_NONE, false, tile);
/* now link IC PP-->MEM to MEM-->IC PP ROT */
ipu_idmac_link(chan->out_chan, chan->rotation_in_chan);
} else {
/* init the destination IC PP-->MEM IDMAC channel */
init_idmac_channel(ctx, chan->out_chan, d_image,
ctx->rot_mode, false, tile);
}
/* enable the IC */
ipu_ic_enable(chan->ic);
/* set buffers ready */
ipu_idmac_select_buffer(chan->in_chan, 0);
ipu_idmac_select_buffer(chan->out_chan, 0);
if (ipu_rot_mode_is_irt(ctx->rot_mode))
ipu_idmac_select_buffer(chan->rotation_out_chan, 0);
if (ctx->double_buffering) {
ipu_idmac_select_buffer(chan->in_chan, 1);
ipu_idmac_select_buffer(chan->out_chan, 1);
if (ipu_rot_mode_is_irt(ctx->rot_mode))
ipu_idmac_select_buffer(chan->rotation_out_chan, 1);
}
/* enable the channels! */
ipu_idmac_enable_channel(chan->in_chan);
ipu_idmac_enable_channel(chan->out_chan);
if (ipu_rot_mode_is_irt(ctx->rot_mode)) {
ipu_idmac_enable_channel(chan->rotation_in_chan);
ipu_idmac_enable_channel(chan->rotation_out_chan);
}
ipu_ic_task_enable(chan->ic);
ipu_cpmem_dump(chan->in_chan);
ipu_cpmem_dump(chan->out_chan);
if (ipu_rot_mode_is_irt(ctx->rot_mode)) {
ipu_cpmem_dump(chan->rotation_in_chan);
ipu_cpmem_dump(chan->rotation_out_chan);
}
ipu_dump(priv->ipu);
return 0;
}
/* hold irqlock when calling */
static int do_run(struct ipu_image_convert_run *run)
{
struct ipu_image_convert_ctx *ctx = run->ctx;
struct ipu_image_convert_chan *chan = ctx->chan;
lockdep_assert_held(&chan->irqlock);
ctx->in.base.phys0 = run->in_phys;
ctx->out.base.phys0 = run->out_phys;
ctx->cur_buf_num = 0;
ctx->next_tile = 1;
/* remove run from pending_q and set as current */
list_del(&run->list);
chan->current_run = run;
return convert_start(run, 0);
}
/* hold irqlock when calling */
static void run_next(struct ipu_image_convert_chan *chan)
{
struct ipu_image_convert_priv *priv = chan->priv;
struct ipu_image_convert_run *run, *tmp;
int ret;
lockdep_assert_held(&chan->irqlock);
list_for_each_entry_safe(run, tmp, &chan->pending_q, list) {
/* skip contexts that are aborting */
if (run->ctx->aborting) {
dev_dbg(priv->ipu->dev,
"%s: task %u: skipping aborting ctx %p run %p\n",
__func__, chan->ic_task, run->ctx, run);
continue;
}
ret = do_run(run);
if (!ret)
break;
/*
* something went wrong with start, add the run
* to done q and continue to the next run in the
* pending q.
*/
run->status = ret;
list_add_tail(&run->list, &chan->done_q);
chan->current_run = NULL;
}
}
static void empty_done_q(struct ipu_image_convert_chan *chan)
{
struct ipu_image_convert_priv *priv = chan->priv;
struct ipu_image_convert_run *run;
unsigned long flags;
spin_lock_irqsave(&chan->irqlock, flags);
while (!list_empty(&chan->done_q)) {
run = list_entry(chan->done_q.next,
struct ipu_image_convert_run,
list);
list_del(&run->list);
dev_dbg(priv->ipu->dev,
"%s: task %u: completing ctx %p run %p with %d\n",
__func__, chan->ic_task, run->ctx, run, run->status);
/* call the completion callback and free the run */
spin_unlock_irqrestore(&chan->irqlock, flags);
run->ctx->complete(run, run->ctx->complete_context);
spin_lock_irqsave(&chan->irqlock, flags);
}
spin_unlock_irqrestore(&chan->irqlock, flags);
}
/*
* the bottom half thread clears out the done_q, calling the
* completion handler for each.
*/
static irqreturn_t do_bh(int irq, void *dev_id)
{
struct ipu_image_convert_chan *chan = dev_id;
struct ipu_image_convert_priv *priv = chan->priv;
struct ipu_image_convert_ctx *ctx;
unsigned long flags;
dev_dbg(priv->ipu->dev, "%s: task %u: enter\n", __func__,
chan->ic_task);
empty_done_q(chan);
spin_lock_irqsave(&chan->irqlock, flags);
/*
* the done_q is cleared out, signal any contexts
* that are aborting that abort can complete.
*/
list_for_each_entry(ctx, &chan->ctx_list, list) {
if (ctx->aborting) {
dev_dbg(priv->ipu->dev,
"%s: task %u: signaling abort for ctx %p\n",
__func__, chan->ic_task, ctx);
complete_all(&ctx->aborted);
}
}
spin_unlock_irqrestore(&chan->irqlock, flags);
dev_dbg(priv->ipu->dev, "%s: task %u: exit\n", __func__,
chan->ic_task);
return IRQ_HANDLED;
}
static bool ic_settings_changed(struct ipu_image_convert_ctx *ctx)
{
unsigned int cur_tile = ctx->next_tile - 1;
unsigned int next_tile = ctx->next_tile;
if (ctx->resize_coeffs_h[cur_tile % ctx->in.num_cols] !=
ctx->resize_coeffs_h[next_tile % ctx->in.num_cols] ||
ctx->resize_coeffs_v[cur_tile / ctx->in.num_cols] !=
ctx->resize_coeffs_v[next_tile / ctx->in.num_cols] ||
ctx->in.tile[cur_tile].width != ctx->in.tile[next_tile].width ||
ctx->in.tile[cur_tile].height != ctx->in.tile[next_tile].height ||
ctx->out.tile[cur_tile].width != ctx->out.tile[next_tile].width ||
ctx->out.tile[cur_tile].height != ctx->out.tile[next_tile].height)
return true;
return false;
}
/* hold irqlock when calling */
static irqreturn_t do_irq(struct ipu_image_convert_run *run)
{
struct ipu_image_convert_ctx *ctx = run->ctx;
struct ipu_image_convert_chan *chan = ctx->chan;
struct ipu_image_tile *src_tile, *dst_tile;
struct ipu_image_convert_image *s_image = &ctx->in;
struct ipu_image_convert_image *d_image = &ctx->out;
struct ipuv3_channel *outch;
unsigned int dst_idx;
lockdep_assert_held(&chan->irqlock);
outch = ipu_rot_mode_is_irt(ctx->rot_mode) ?
chan->rotation_out_chan : chan->out_chan;
/*
* It is difficult to stop the channel DMA before the channels
* enter the paused state. Without double-buffering the channels
* are always in a paused state when the EOF irq occurs, so it
* is safe to stop the channels now. For double-buffering we
* just ignore the abort until the operation completes, when it
* is safe to shut down.
*/
if (ctx->aborting && !ctx->double_buffering) {
convert_stop(run);
run->status = -EIO;
goto done;
}
if (ctx->next_tile == ctx->num_tiles) {
/*
* the conversion is complete
*/
convert_stop(run);
run->status = 0;
goto done;
}
/*
* not done, place the next tile buffers.
*/
if (!ctx->double_buffering) {
if (ic_settings_changed(ctx)) {
convert_stop(run);
convert_start(run, ctx->next_tile);
} else {
src_tile = &s_image->tile[ctx->next_tile];
dst_idx = ctx->out_tile_map[ctx->next_tile];
dst_tile = &d_image->tile[dst_idx];
ipu_cpmem_set_buffer(chan->in_chan, 0,
s_image->base.phys0 +
src_tile->offset);
ipu_cpmem_set_buffer(outch, 0,
d_image->base.phys0 +
dst_tile->offset);
if (s_image->fmt->planar)
ipu_cpmem_set_uv_offset(chan->in_chan,
src_tile->u_off,
src_tile->v_off);
if (d_image->fmt->planar)
ipu_cpmem_set_uv_offset(outch,
dst_tile->u_off,
dst_tile->v_off);
ipu_idmac_select_buffer(chan->in_chan, 0);
ipu_idmac_select_buffer(outch, 0);
}
} else if (ctx->next_tile < ctx->num_tiles - 1) {
src_tile = &s_image->tile[ctx->next_tile + 1];
dst_idx = ctx->out_tile_map[ctx->next_tile + 1];
dst_tile = &d_image->tile[dst_idx];
ipu_cpmem_set_buffer(chan->in_chan, ctx->cur_buf_num,
s_image->base.phys0 + src_tile->offset);
ipu_cpmem_set_buffer(outch, ctx->cur_buf_num,
d_image->base.phys0 + dst_tile->offset);
ipu_idmac_select_buffer(chan->in_chan, ctx->cur_buf_num);
ipu_idmac_select_buffer(outch, ctx->cur_buf_num);
ctx->cur_buf_num ^= 1;
}
ctx->next_tile++;
return IRQ_HANDLED;
done:
list_add_tail(&run->list, &chan->done_q);
chan->current_run = NULL;
run_next(chan);
return IRQ_WAKE_THREAD;
}
static irqreturn_t norotate_irq(int irq, void *data)
{
struct ipu_image_convert_chan *chan = data;
struct ipu_image_convert_ctx *ctx;
struct ipu_image_convert_run *run;
unsigned long flags;
irqreturn_t ret;
spin_lock_irqsave(&chan->irqlock, flags);
/* get current run and its context */
run = chan->current_run;
if (!run) {
ret = IRQ_NONE;
goto out;
}
ctx = run->ctx;
if (ipu_rot_mode_is_irt(ctx->rot_mode)) {
/* this is a rotation operation, just ignore */
spin_unlock_irqrestore(&chan->irqlock, flags);
return IRQ_HANDLED;
}
ret = do_irq(run);
out:
spin_unlock_irqrestore(&chan->irqlock, flags);
return ret;
}
static irqreturn_t rotate_irq(int irq, void *data)
{
struct ipu_image_convert_chan *chan = data;
struct ipu_image_convert_priv *priv = chan->priv;
struct ipu_image_convert_ctx *ctx;
struct ipu_image_convert_run *run;
unsigned long flags;
irqreturn_t ret;
spin_lock_irqsave(&chan->irqlock, flags);
/* get current run and its context */
run = chan->current_run;
if (!run) {
ret = IRQ_NONE;
goto out;
}
ctx = run->ctx;
if (!ipu_rot_mode_is_irt(ctx->rot_mode)) {
/* this was NOT a rotation operation, shouldn't happen */
dev_err(priv->ipu->dev, "Unexpected rotation interrupt\n");
spin_unlock_irqrestore(&chan->irqlock, flags);
return IRQ_HANDLED;
}
ret = do_irq(run);
out:
spin_unlock_irqrestore(&chan->irqlock, flags);
return ret;
}
/*
* try to force the completion of runs for this ctx. Called when
* abort wait times out in ipu_image_convert_abort().
*/
static void force_abort(struct ipu_image_convert_ctx *ctx)
{
struct ipu_image_convert_chan *chan = ctx->chan;
struct ipu_image_convert_run *run;
unsigned long flags;
spin_lock_irqsave(&chan->irqlock, flags);
run = chan->current_run;
if (run && run->ctx == ctx) {
convert_stop(run);
run->status = -EIO;
list_add_tail(&run->list, &chan->done_q);
chan->current_run = NULL;
run_next(chan);
}
spin_unlock_irqrestore(&chan->irqlock, flags);
empty_done_q(chan);
}
static void release_ipu_resources(struct ipu_image_convert_chan *chan)
{
if (chan->out_eof_irq >= 0)
free_irq(chan->out_eof_irq, chan);
if (chan->rot_out_eof_irq >= 0)
free_irq(chan->rot_out_eof_irq, chan);
if (!IS_ERR_OR_NULL(chan->in_chan))
ipu_idmac_put(chan->in_chan);
if (!IS_ERR_OR_NULL(chan->out_chan))
ipu_idmac_put(chan->out_chan);
if (!IS_ERR_OR_NULL(chan->rotation_in_chan))
ipu_idmac_put(chan->rotation_in_chan);
if (!IS_ERR_OR_NULL(chan->rotation_out_chan))
ipu_idmac_put(chan->rotation_out_chan);
if (!IS_ERR_OR_NULL(chan->ic))
ipu_ic_put(chan->ic);
chan->in_chan = chan->out_chan = chan->rotation_in_chan =
chan->rotation_out_chan = NULL;
chan->out_eof_irq = chan->rot_out_eof_irq = -1;
}
static int get_ipu_resources(struct ipu_image_convert_chan *chan)
{
const struct ipu_image_convert_dma_chan *dma = chan->dma_ch;
struct ipu_image_convert_priv *priv = chan->priv;
int ret;
/* get IC */
chan->ic = ipu_ic_get(priv->ipu, chan->ic_task);
if (IS_ERR(chan->ic)) {
dev_err(priv->ipu->dev, "could not acquire IC\n");
ret = PTR_ERR(chan->ic);
goto err;
}
/* get IDMAC channels */
chan->in_chan = ipu_idmac_get(priv->ipu, dma->in);
chan->out_chan = ipu_idmac_get(priv->ipu, dma->out);
if (IS_ERR(chan->in_chan) || IS_ERR(chan->out_chan)) {
dev_err(priv->ipu->dev, "could not acquire idmac channels\n");
ret = -EBUSY;
goto err;
}
chan->rotation_in_chan = ipu_idmac_get(priv->ipu, dma->rot_in);
chan->rotation_out_chan = ipu_idmac_get(priv->ipu, dma->rot_out);
if (IS_ERR(chan->rotation_in_chan) || IS_ERR(chan->rotation_out_chan)) {
dev_err(priv->ipu->dev,
"could not acquire idmac rotation channels\n");
ret = -EBUSY;
goto err;
}
/* acquire the EOF interrupts */
chan->out_eof_irq = ipu_idmac_channel_irq(priv->ipu,
chan->out_chan,
IPU_IRQ_EOF);
ret = request_threaded_irq(chan->out_eof_irq, norotate_irq, do_bh,
0, "ipu-ic", chan);
if (ret < 0) {
dev_err(priv->ipu->dev, "could not acquire irq %d\n",
chan->out_eof_irq);
chan->out_eof_irq = -1;
goto err;
}
chan->rot_out_eof_irq = ipu_idmac_channel_irq(priv->ipu,
chan->rotation_out_chan,
IPU_IRQ_EOF);
ret = request_threaded_irq(chan->rot_out_eof_irq, rotate_irq, do_bh,
0, "ipu-ic", chan);
if (ret < 0) {
dev_err(priv->ipu->dev, "could not acquire irq %d\n",
chan->rot_out_eof_irq);
chan->rot_out_eof_irq = -1;
goto err;
}
return 0;
err:
release_ipu_resources(chan);
return ret;
}
static int fill_image(struct ipu_image_convert_ctx *ctx,
struct ipu_image_convert_image *ic_image,
struct ipu_image *image,
enum ipu_image_convert_type type)
{
struct ipu_image_convert_priv *priv = ctx->chan->priv;
ic_image->base = *image;
ic_image->type = type;
ic_image->fmt = get_format(image->pix.pixelformat);
if (!ic_image->fmt) {
dev_err(priv->ipu->dev, "pixelformat not supported for %s\n",
type == IMAGE_CONVERT_OUT ? "Output" : "Input");
return -EINVAL;
}
if (ic_image->fmt->planar)
ic_image->stride = ic_image->base.pix.width;
else
ic_image->stride = ic_image->base.pix.bytesperline;
return 0;
}
/* borrowed from drivers/media/v4l2-core/v4l2-common.c */
static unsigned int clamp_align(unsigned int x, unsigned int min,
unsigned int max, unsigned int align)
{
/* Bits that must be zero to be aligned */
unsigned int mask = ~((1 << align) - 1);
/* Clamp to aligned min and max */
x = clamp(x, (min + ~mask) & mask, max & mask);
/* Round to nearest aligned value */
if (align)
x = (x + (1 << (align - 1))) & mask;
return x;
}
/* Adjusts input/output images to IPU restrictions */
void ipu_image_convert_adjust(struct ipu_image *in, struct ipu_image *out,
enum ipu_rotate_mode rot_mode)
{
const struct ipu_image_pixfmt *infmt, *outfmt;
u32 w_align_out, h_align_out;
u32 w_align_in, h_align_in;
infmt = get_format(in->pix.pixelformat);
outfmt = get_format(out->pix.pixelformat);
/* set some default pixel formats if needed */
if (!infmt) {
in->pix.pixelformat = V4L2_PIX_FMT_RGB24;
infmt = get_format(V4L2_PIX_FMT_RGB24);
}
if (!outfmt) {
out->pix.pixelformat = V4L2_PIX_FMT_RGB24;
outfmt = get_format(V4L2_PIX_FMT_RGB24);
}
/* image converter does not handle fields */
in->pix.field = out->pix.field = V4L2_FIELD_NONE;
/* resizer cannot downsize more than 4:1 */
if (ipu_rot_mode_is_irt(rot_mode)) {
out->pix.height = max_t(__u32, out->pix.height,
in->pix.width / 4);
out->pix.width = max_t(__u32, out->pix.width,
in->pix.height / 4);
} else {
out->pix.width = max_t(__u32, out->pix.width,
in->pix.width / 4);
out->pix.height = max_t(__u32, out->pix.height,
in->pix.height / 4);
}
/* align input width/height */
w_align_in = ilog2(tile_width_align(IMAGE_CONVERT_IN, infmt,
rot_mode));
h_align_in = ilog2(tile_height_align(IMAGE_CONVERT_IN, infmt,
rot_mode));
in->pix.width = clamp_align(in->pix.width, MIN_W, MAX_W,
w_align_in);
in->pix.height = clamp_align(in->pix.height, MIN_H, MAX_H,
h_align_in);
/* align output width/height */
w_align_out = ilog2(tile_width_align(IMAGE_CONVERT_OUT, outfmt,
rot_mode));
h_align_out = ilog2(tile_height_align(IMAGE_CONVERT_OUT, outfmt,
rot_mode));
out->pix.width = clamp_align(out->pix.width, MIN_W, MAX_W,
w_align_out);
out->pix.height = clamp_align(out->pix.height, MIN_H, MAX_H,
h_align_out);
/* set input/output strides and image sizes */
in->pix.bytesperline = infmt->planar ?
clamp_align(in->pix.width, 2 << w_align_in, MAX_W,
w_align_in) :
clamp_align((in->pix.width * infmt->bpp) >> 3,
((2 << w_align_in) * infmt->bpp) >> 3,
(MAX_W * infmt->bpp) >> 3,
w_align_in);
in->pix.sizeimage = infmt->planar ?
(in->pix.height * in->pix.bytesperline * infmt->bpp) >> 3 :
in->pix.height * in->pix.bytesperline;
out->pix.bytesperline = outfmt->planar ? out->pix.width :
(out->pix.width * outfmt->bpp) >> 3;
out->pix.sizeimage = outfmt->planar ?
(out->pix.height * out->pix.bytesperline * outfmt->bpp) >> 3 :
out->pix.height * out->pix.bytesperline;
}
EXPORT_SYMBOL_GPL(ipu_image_convert_adjust);
/*
* this is used by ipu_image_convert_prepare() to verify set input and
* output images are valid before starting the conversion. Clients can
* also call it before calling ipu_image_convert_prepare().
*/
int ipu_image_convert_verify(struct ipu_image *in, struct ipu_image *out,
enum ipu_rotate_mode rot_mode)
{
struct ipu_image testin, testout;
testin = *in;
testout = *out;
ipu_image_convert_adjust(&testin, &testout, rot_mode);
if (testin.pix.width != in->pix.width ||
testin.pix.height != in->pix.height ||
testout.pix.width != out->pix.width ||
testout.pix.height != out->pix.height)
return -EINVAL;
return 0;
}
EXPORT_SYMBOL_GPL(ipu_image_convert_verify);
/*
* Call ipu_image_convert_prepare() to prepare for the conversion of
* given images and rotation mode. Returns a new conversion context.
*/
struct ipu_image_convert_ctx *
ipu_image_convert_prepare(struct ipu_soc *ipu, enum ipu_ic_task ic_task,
struct ipu_image *in, struct ipu_image *out,
enum ipu_rotate_mode rot_mode,
ipu_image_convert_cb_t complete,
void *complete_context)
{
struct ipu_image_convert_priv *priv = ipu->image_convert_priv;
struct ipu_image_convert_image *s_image, *d_image;
struct ipu_image_convert_chan *chan;
struct ipu_image_convert_ctx *ctx;
unsigned long flags;
unsigned int i;
bool get_res;
int ret;
if (!in || !out || !complete ||
(ic_task != IC_TASK_VIEWFINDER &&
ic_task != IC_TASK_POST_PROCESSOR))
return ERR_PTR(-EINVAL);
/* verify the in/out images before continuing */
ret = ipu_image_convert_verify(in, out, rot_mode);
if (ret) {
dev_err(priv->ipu->dev, "%s: in/out formats invalid\n",
__func__);
return ERR_PTR(ret);
}
chan = &priv->chan[ic_task];
ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
if (!ctx)
return ERR_PTR(-ENOMEM);
dev_dbg(priv->ipu->dev, "%s: task %u: ctx %p\n", __func__,
chan->ic_task, ctx);
ctx->chan = chan;
init_completion(&ctx->aborted);
ctx->rot_mode = rot_mode;
/* Sets ctx->in.num_rows/cols as well */
ret = calc_image_resize_coefficients(ctx, in, out);
if (ret)
goto out_free;
s_image = &ctx->in;
d_image = &ctx->out;
/* set tiling and rotation */
if (ipu_rot_mode_is_irt(rot_mode)) {
d_image->num_rows = s_image->num_cols;
d_image->num_cols = s_image->num_rows;
} else {
d_image->num_rows = s_image->num_rows;
d_image->num_cols = s_image->num_cols;
}
ctx->num_tiles = d_image->num_cols * d_image->num_rows;
ret = fill_image(ctx, s_image, in, IMAGE_CONVERT_IN);
if (ret)
goto out_free;
ret = fill_image(ctx, d_image, out, IMAGE_CONVERT_OUT);
if (ret)
goto out_free;
calc_out_tile_map(ctx);
find_seams(ctx, s_image, d_image);
ret = calc_tile_dimensions(ctx, s_image);
if (ret)
goto out_free;
ret = calc_tile_offsets(ctx, s_image);
if (ret)
goto out_free;
calc_tile_dimensions(ctx, d_image);
ret = calc_tile_offsets(ctx, d_image);
if (ret)
goto out_free;
calc_tile_resize_coefficients(ctx);
ret = ipu_ic_calc_csc(&ctx->csc,
s_image->base.pix.ycbcr_enc,
s_image->base.pix.quantization,
ipu_pixelformat_to_colorspace(s_image->fmt->fourcc),
d_image->base.pix.ycbcr_enc,
d_image->base.pix.quantization,
ipu_pixelformat_to_colorspace(d_image->fmt->fourcc));
if (ret)
goto out_free;
dump_format(ctx, s_image);
dump_format(ctx, d_image);
ctx->complete = complete;
ctx->complete_context = complete_context;
/*
* Can we use double-buffering for this operation? If there is
* only one tile (the whole image can be converted in a single
* operation) there's no point in using double-buffering. Also,
* the IPU's IDMAC channels allow only a single U and V plane
* offset shared between both buffers, but these offsets change
* for every tile, and therefore would have to be updated for
* each buffer which is not possible. So double-buffering is
* impossible when either the source or destination images are
* a planar format (YUV420, YUV422P, etc.). Further, differently
* sized tiles or different resizing coefficients per tile
* prevent double-buffering as well.
*/
ctx->double_buffering = (ctx->num_tiles > 1 &&
!s_image->fmt->planar &&
!d_image->fmt->planar);
for (i = 1; i < ctx->num_tiles; i++) {
if (ctx->in.tile[i].width != ctx->in.tile[0].width ||
ctx->in.tile[i].height != ctx->in.tile[0].height ||
ctx->out.tile[i].width != ctx->out.tile[0].width ||
ctx->out.tile[i].height != ctx->out.tile[0].height) {
ctx->double_buffering = false;
break;
}
}
for (i = 1; i < ctx->in.num_cols; i++) {
if (ctx->resize_coeffs_h[i] != ctx->resize_coeffs_h[0]) {
ctx->double_buffering = false;
break;
}
}
for (i = 1; i < ctx->in.num_rows; i++) {
if (ctx->resize_coeffs_v[i] != ctx->resize_coeffs_v[0]) {
ctx->double_buffering = false;
break;
}
}
if (ipu_rot_mode_is_irt(ctx->rot_mode)) {
unsigned long intermediate_size = d_image->tile[0].size;
for (i = 1; i < ctx->num_tiles; i++) {
if (d_image->tile[i].size > intermediate_size)
intermediate_size = d_image->tile[i].size;
}
ret = alloc_dma_buf(priv, &ctx->rot_intermediate[0],
intermediate_size);
if (ret)
goto out_free;
if (ctx->double_buffering) {
ret = alloc_dma_buf(priv,
&ctx->rot_intermediate[1],
intermediate_size);
if (ret)
goto out_free_dmabuf0;
}
}
spin_lock_irqsave(&chan->irqlock, flags);
get_res = list_empty(&chan->ctx_list);
list_add_tail(&ctx->list, &chan->ctx_list);
spin_unlock_irqrestore(&chan->irqlock, flags);
if (get_res) {
ret = get_ipu_resources(chan);
if (ret)
goto out_free_dmabuf1;
}
return ctx;
out_free_dmabuf1:
free_dma_buf(priv, &ctx->rot_intermediate[1]);
spin_lock_irqsave(&chan->irqlock, flags);
list_del(&ctx->list);
spin_unlock_irqrestore(&chan->irqlock, flags);
out_free_dmabuf0:
free_dma_buf(priv, &ctx->rot_intermediate[0]);
out_free:
kfree(ctx);
return ERR_PTR(ret);
}
EXPORT_SYMBOL_GPL(ipu_image_convert_prepare);
/*
* Carry out a single image conversion run. Only the physaddr's of the input
* and output image buffers are needed. The conversion context must have
* been created previously with ipu_image_convert_prepare().
*/
int ipu_image_convert_queue(struct ipu_image_convert_run *run)
{
struct ipu_image_convert_chan *chan;
struct ipu_image_convert_priv *priv;
struct ipu_image_convert_ctx *ctx;
unsigned long flags;
int ret = 0;
if (!run || !run->ctx || !run->in_phys || !run->out_phys)
return -EINVAL;
ctx = run->ctx;
chan = ctx->chan;
priv = chan->priv;
dev_dbg(priv->ipu->dev, "%s: task %u: ctx %p run %p\n", __func__,
chan->ic_task, ctx, run);
INIT_LIST_HEAD(&run->list);
spin_lock_irqsave(&chan->irqlock, flags);
if (ctx->aborting) {
ret = -EIO;
goto unlock;
}
list_add_tail(&run->list, &chan->pending_q);
if (!chan->current_run) {
ret = do_run(run);
if (ret)
chan->current_run = NULL;
}
unlock:
spin_unlock_irqrestore(&chan->irqlock, flags);
return ret;
}
EXPORT_SYMBOL_GPL(ipu_image_convert_queue);
/* Abort any active or pending conversions for this context */
static void __ipu_image_convert_abort(struct ipu_image_convert_ctx *ctx)
{
struct ipu_image_convert_chan *chan = ctx->chan;
struct ipu_image_convert_priv *priv = chan->priv;
struct ipu_image_convert_run *run, *active_run, *tmp;
unsigned long flags;
int run_count, ret;
spin_lock_irqsave(&chan->irqlock, flags);
/* move all remaining pending runs in this context to done_q */
list_for_each_entry_safe(run, tmp, &chan->pending_q, list) {
if (run->ctx != ctx)
continue;
run->status = -EIO;
list_move_tail(&run->list, &chan->done_q);
}
run_count = get_run_count(ctx, &chan->done_q);
active_run = (chan->current_run && chan->current_run->ctx == ctx) ?
chan->current_run : NULL;
if (active_run)
reinit_completion(&ctx->aborted);
ctx->aborting = true;
spin_unlock_irqrestore(&chan->irqlock, flags);
if (!run_count && !active_run) {
dev_dbg(priv->ipu->dev,
"%s: task %u: no abort needed for ctx %p\n",
__func__, chan->ic_task, ctx);
return;
}
if (!active_run) {
empty_done_q(chan);
return;
}
dev_dbg(priv->ipu->dev,
"%s: task %u: wait for completion: %d runs\n",
__func__, chan->ic_task, run_count);
ret = wait_for_completion_timeout(&ctx->aborted,
msecs_to_jiffies(10000));
if (ret == 0) {
dev_warn(priv->ipu->dev, "%s: timeout\n", __func__);
force_abort(ctx);
}
}
void ipu_image_convert_abort(struct ipu_image_convert_ctx *ctx)
{
__ipu_image_convert_abort(ctx);
ctx->aborting = false;
}
EXPORT_SYMBOL_GPL(ipu_image_convert_abort);
/* Unprepare image conversion context */
void ipu_image_convert_unprepare(struct ipu_image_convert_ctx *ctx)
{
struct ipu_image_convert_chan *chan = ctx->chan;
struct ipu_image_convert_priv *priv = chan->priv;
unsigned long flags;
bool put_res;
/* make sure no runs are hanging around */
__ipu_image_convert_abort(ctx);
dev_dbg(priv->ipu->dev, "%s: task %u: removing ctx %p\n", __func__,
chan->ic_task, ctx);
spin_lock_irqsave(&chan->irqlock, flags);
list_del(&ctx->list);
put_res = list_empty(&chan->ctx_list);
spin_unlock_irqrestore(&chan->irqlock, flags);
if (put_res)
release_ipu_resources(chan);
free_dma_buf(priv, &ctx->rot_intermediate[1]);
free_dma_buf(priv, &ctx->rot_intermediate[0]);
kfree(ctx);
}
EXPORT_SYMBOL_GPL(ipu_image_convert_unprepare);
/*
* "Canned" asynchronous single image conversion. Allocates and returns
* a new conversion run. On successful return the caller must free the
* run and call ipu_image_convert_unprepare() after conversion completes.
*/
struct ipu_image_convert_run *
ipu_image_convert(struct ipu_soc *ipu, enum ipu_ic_task ic_task,
struct ipu_image *in, struct ipu_image *out,
enum ipu_rotate_mode rot_mode,
ipu_image_convert_cb_t complete,
void *complete_context)
{
struct ipu_image_convert_ctx *ctx;
struct ipu_image_convert_run *run;
int ret;
ctx = ipu_image_convert_prepare(ipu, ic_task, in, out, rot_mode,
complete, complete_context);
if (IS_ERR(ctx))
return ERR_CAST(ctx);
run = kzalloc(sizeof(*run), GFP_KERNEL);
if (!run) {
ipu_image_convert_unprepare(ctx);
return ERR_PTR(-ENOMEM);
}
run->ctx = ctx;
run->in_phys = in->phys0;
run->out_phys = out->phys0;
ret = ipu_image_convert_queue(run);
if (ret) {
ipu_image_convert_unprepare(ctx);
kfree(run);
return ERR_PTR(ret);
}
return run;
}
EXPORT_SYMBOL_GPL(ipu_image_convert);
/* "Canned" synchronous single image conversion */
static void image_convert_sync_complete(struct ipu_image_convert_run *run,
void *data)
{
struct completion *comp = data;
complete(comp);
}
int ipu_image_convert_sync(struct ipu_soc *ipu, enum ipu_ic_task ic_task,
struct ipu_image *in, struct ipu_image *out,
enum ipu_rotate_mode rot_mode)
{
struct ipu_image_convert_run *run;
struct completion comp;
int ret;
init_completion(&comp);
run = ipu_image_convert(ipu, ic_task, in, out, rot_mode,
image_convert_sync_complete, &comp);
if (IS_ERR(run))
return PTR_ERR(run);
ret = wait_for_completion_timeout(&comp, msecs_to_jiffies(10000));
ret = (ret == 0) ? -ETIMEDOUT : 0;
ipu_image_convert_unprepare(run->ctx);
kfree(run);
return ret;
}
EXPORT_SYMBOL_GPL(ipu_image_convert_sync);
int ipu_image_convert_init(struct ipu_soc *ipu, struct device *dev)
{
struct ipu_image_convert_priv *priv;
int i;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
ipu->image_convert_priv = priv;
priv->ipu = ipu;
for (i = 0; i < IC_NUM_TASKS; i++) {
struct ipu_image_convert_chan *chan = &priv->chan[i];
chan->ic_task = i;
chan->priv = priv;
chan->dma_ch = &image_convert_dma_chan[i];
chan->out_eof_irq = -1;
chan->rot_out_eof_irq = -1;
spin_lock_init(&chan->irqlock);
INIT_LIST_HEAD(&chan->ctx_list);
INIT_LIST_HEAD(&chan->pending_q);
INIT_LIST_HEAD(&chan->done_q);
}
return 0;
}
void ipu_image_convert_exit(struct ipu_soc *ipu)
{
}