blob: 96da44721c88bcf67a98c62fdd5a23c3a4d9829a [file] [log] [blame]
#include <linux/module.h>
#include "avi_scaler.h"
/**************************
* Scalers low-level setup
**************************/
struct avi_scal_dimcfg {
unsigned ntaps;
unsigned nphases;
char coeffs[32];
};
/* Configuration for scaling factor 1 / 1 using lanczos
* with 2 lobes.
*
* This configuration uses 4 taps and 8 phases.
*
* +
* +|+
* | +
* + |
* |
* + | +
* |
* + | +
* |
* |
* + | +
* |
* |
* + | +
* |
* + | +
* -------+---------------+------++
* ++++ + | + +++
* ++ | ++
*/
static const struct avi_scal_dimcfg avi_scalcfg_1_1 = {
.ntaps = AVI_SCAL_NTAPS_4,
.nphases = AVI_SCAL_NPHASES_8,
.coeffs = {
0, -4, -5, -5, -4, -2, -1, -1, /* tap 0 */
64, 62, 55, 46, 36, 24, 14, 6, /* tap 1 */
0, 6, 15, 25, 36, 47, 56, 62, /* tap 2 */
0, 0, -1, -2, -4, -5, -5, -3, /* tap 3 */
},
};
/* Configuration for scaling factor 1 / 2 using lanczos
* with 2 lobes.
*
* This configuration uses 8 taps and 4 phases.
*
* +++
* + | +
* + | +
* + | +
* |
* + | +
* + | +
* + | +
* ++++-+++---------------+++++++++
* + |
*/
static const struct avi_scal_dimcfg avi_scalcfg_1_2 = {
.ntaps = AVI_SCAL_NTAPS_8,
.nphases = AVI_SCAL_NPHASES_4,
.coeffs = {
-2, -1, -1, -1, /* tap 0 */
0, -2, -2, -3, /* tap 1 */
18, 13, 7, 3, /* tap 2 */
32, 31, 28, 23, /* tap 3 */
18, 23, 28, 31, /* tap 4 */
0, 3, 7, 13, /* tap 5 */
-1, -2, -2, -1, /* tap 6 */
-1, -1, -1, -1, /* tap 7 */
},
};
/* Configuration for scaling factor 1 / 3 using lanczos
* with 2 lobes.
*
* This configuration uses 16 taps and 2 phases.
*
* +++
* + | +
* + | +
* + | +
* + | +
* ++++++++++-----------+++++++++++
*/
static const struct avi_scal_dimcfg avi_scalcfg_1_3 = {
.ntaps = AVI_SCAL_NTAPS_16,
.nphases = AVI_SCAL_NPHASES_2,
.coeffs = {
-1, -1, /* tap 0 */
-1, -1, /* tap 1 */
0, 0, /* tap 2 */
-1, 0, /* tap 3 */
0, -1, /* tap 4 */
7, 3, /* tap 5 */
17, 12, /* tap 6 */
21, 20, /* tap 7 */
17, 20, /* tap 8 */
7, 12, /* tap 9 */
0, 3, /* tap 10 */
-1, -1, /* tap 11 */
0, 0, /* tap 12 */
0, 0, /* tap 13 */
0, -1, /* tap 14 */
-1, -1, /* tap 15 */
},
};
/* Configuration for scaling factor 2 / 3 using lanczos
* with 2 lobes.
*
* This configuration uses 8 taps and 4 phases.
*
* +
* +|+
* |
* + | +
* |
* + | +
* |
* + | +
* |
* + | +
* |
* +++++++-++-----------++-++++++++
* + | +
*/
static const struct avi_scal_dimcfg avi_scalcfg_2_3 = {
.ntaps = AVI_SCAL_NTAPS_8,
.nphases = AVI_SCAL_NPHASES_4,
.coeffs = {
0, -1, -1, -1, /* tap 0 */
-3, -2, 0, 0, /* tap 1 */
14, 6, 0, -2, /* tap 2 */
42, 40, 33, 24, /* tap 3 */
14, 24, 33, 40, /* tap 4 */
-3, -2, 0, 6, /* tap 5 */
0, 0, 0, -2, /* tap 6 */
0, -1, -1, -1, /* tap 7 */
},
};
/* Configuration for scaling factor 3 / 4 using lanczos
* with 2 lobes.
*
* This configuration uses 8 taps and 4 phases.
*
* +
* +|+
* |
* + | +
* |
* |
* + | +
* |
* |
* + | +
* |
* + | +
* ++++++++-+-----------+-+++++++++
* + | +
*/
static const struct avi_scal_dimcfg avi_scalcfg_3_4 = {
.ntaps = AVI_SCAL_NTAPS_8,
.nphases = AVI_SCAL_NPHASES_4,
.coeffs = {
-1, -1, -1, -1, /* tap 0 */
-2, 0, 0, 0, /* tap 1 */
11, 2, -2, -3, /* tap 2 */
48, 44, 35, 23, /* tap 3 */
11, 23, 35, 44, /* tap 4 */
-2, -3, -2, 2, /* tap 5 */
0, 0, 0, 0, /* tap 6 */
-1, -1, -1, -1, /* tap 7 */
},
};
/* Configuration for scaling factor 1 / 4 using lanczos
* with 2 lobes.
*
* This configuration uses 16 taps and 2 phases.
*
* +++++
* + | +
* ++ | ++
* ++ | ++
* ++++++++---------------+++++++++
*/
static const struct avi_scal_dimcfg avi_scalcfg_1_4 = {
.ntaps = AVI_SCAL_NTAPS_16,
.nphases = AVI_SCAL_NPHASES_2,
.coeffs = {
-1, -1, /* tap 0 */
-1, -1, /* tap 1 */
-1, -1, /* tap 2 */
0, 0, /* tap 3 */
4, 2, /* tap 4 */
9, 6, /* tap 5 */
14, 12, /* tap 6 */
16, 15, /* tap 7 */
14, 15, /* tap 8 */
9, 12, /* tap 9 */
4, 6, /* tap 10 */
0, 2, /* tap 11 */
0, 0, /* tap 12 */
-1, -1, /* tap 13 */
-1, -1, /* tap 14 */
-1, -1, /* tap 15 */
},
};
#define SCAL_RATIO(_to, _from) ((_to) << 8 / (_from))
struct avi_scal_ratio_config {
unsigned int ratio;
struct avi_scal_dimcfg const* cfg;
};
static const struct avi_scal_ratio_config avi_scal_ratios[] = {
#define ADD_SCAL_CFG(_to, _from) \
{ .ratio = SCAL_RATIO(_to, _from), &avi_scalcfg_ ##_to##_##_from }
/* the matrix is the same for all upscaling ratios. It's also the default ratio */
ADD_SCAL_CFG(1, 1),
/* Various downscaling ratios. Sorted from smaller to bigger. */
ADD_SCAL_CFG(3, 4),
ADD_SCAL_CFG(2, 3),
ADD_SCAL_CFG(1, 2),
ADD_SCAL_CFG(1, 3),
ADD_SCAL_CFG(1, 4),
#undef ADD_SCAL_CFG
};
static inline unsigned avi_scal_delta(unsigned a, unsigned b)
{
if (a > b)
return a - b;
return b - a;
}
/*
* Return scaler's FIR parameters according to sizes passed in argument for one
* dimension.
*/
static struct avi_scal_dimcfg const* avi_scal_size2dimcfg(unsigned from,
unsigned to,
unsigned max_taps)
{
unsigned long ratio = SCAL_RATIO(to, from);
const struct avi_scal_ratio_config *best = &avi_scal_ratios[0];
int i;
for (i = 1; i < ARRAY_SIZE(avi_scal_ratios); i++) {
const struct avi_scal_ratio_config *cur = &avi_scal_ratios[i];
if (cur->cfg->ntaps > max_taps)
continue;
if (avi_scal_delta(cur->ratio, ratio) <
avi_scal_delta(best->ratio, ratio))
best = cur;
}
return best->cfg;
}
static inline void avi_write_scal_coeffs(unsigned long base,
const char *coeffs)
{
#define SCAL_COEFFS_AT(_x) (coeffs[_x] | \
(coeffs[_x + 1] << 8) | \
(coeffs[_x + 2] << 16) | \
(coeffs[_x + 3] << 24))
AVI_WRITE(SCAL_COEFFS_AT(0), base + AVI_SCAL_COEFF0300);
AVI_WRITE(SCAL_COEFFS_AT(4), base + AVI_SCAL_COEFF0704);
AVI_WRITE(SCAL_COEFFS_AT(8), base + AVI_SCAL_COEFF1108);
AVI_WRITE(SCAL_COEFFS_AT(12), base + AVI_SCAL_COEFF1512);
AVI_WRITE(SCAL_COEFFS_AT(16), base + AVI_SCAL_COEFF1916);
AVI_WRITE(SCAL_COEFFS_AT(20), base + AVI_SCAL_COEFF2320);
AVI_WRITE(SCAL_COEFFS_AT(24), base + AVI_SCAL_COEFF2724);
AVI_WRITE(SCAL_COEFFS_AT(28), base + AVI_SCAL_COEFF3128);
#undef SCAL_COEFFS_AT
}
/*
* Setup scaler's parameters for resizing a single plane (handling both
* dimensions if required).
* A NULL increment means no resize operation is requested for the given
* dimension.
* A NULL dimension configuration requests to leave coefficients unchanged.
*/
static void avi_setup_scalplane(unsigned long base,
u32 woffset,
u32 wincr,
struct avi_scal_dimcfg const* wcfg,
u32 hoffset,
u32 hincr,
struct avi_scal_dimcfg const* hcfg)
{
u32 tp = 0;
if (wincr) {
/* Horizontal resizing requested. */
tp = wcfg->ntaps << AVI_SCAL_NTAPSX_SHIFT |
wcfg->nphases << AVI_SCAL_NPHASESX_SHIFT;
AVI_WRITE(woffset,
base + AVI_SCAL_HORIZ_DIM + AVI_SCAL_OFFSET);
AVI_WRITE(wincr, base + AVI_SCAL_HORIZ_DIM + AVI_SCAL_INCR);
/* Setup FIR coefficients for horizontal dimension. */
avi_write_scal_coeffs(base + AVI_SCAL_HORIZ_DIM, wcfg->coeffs);
}
if (hincr) {
/* Vertical resizing requested. */
tp |= hcfg->ntaps << AVI_SCAL_NTAPSY_SHIFT |
hcfg->nphases << AVI_SCAL_NPHASESY_SHIFT;
AVI_WRITE(hoffset,
base + AVI_SCAL_VERT_DIM + AVI_SCAL_OFFSET);
AVI_WRITE(hincr, base + AVI_SCAL_VERT_DIM + AVI_SCAL_INCR);
AVI_WRITE(hoffset,
base + AVI_SCAL_VERT_DIM + AVI_SCAL_OFFSET);
AVI_WRITE(hincr, base + AVI_SCAL_VERT_DIM + AVI_SCAL_INCR);
/* Setup FIR coefficients for vertical dimension. */
avi_write_scal_coeffs(base + AVI_SCAL_VERT_DIM, hcfg->coeffs);
}
/* Setup FIR number of phases and taps for both dimensions. */
AVI_WRITE(tp, base + AVI_SCAL_NTAPS_NPHASES);
}
/* Compute the base offset value in Q16.8.
*
* This value depends solely on the scaling factor.
*/
static inline unsigned avi_scal_offset(u16 from, u16 to)
{
/* The offset is (F - 1) / 2, where F is the downscaling factor. This
* means that for upscaling (when F is smaller than 1) the offset is
* negative.
*
* The offset is stored in hardware in Q16.8, so we have to multiply of
* final offset value by 256. We can simplify ((F - 1) * 256 / 2) as
* (128 * F - 128) */
unsigned offset = ((from << 7) + to - 1) / to;
/* Here if offset is < 128 it means we are upscaling and the offset
* should be negative. However, the hardware doesn't handle negative
* offsets, so in this case I cheat by moving one pixel right and making
* the effective offset (1 + real_offset). This means we're skewed by
* one pixel but the phases still fall in the right places */
if (offset < 128)
return offset + 128; /* move one pixel to the right to get a
* positive value with the same phase */
return offset - 128; /* true value */
}
unsigned avi_scal_line_taps(unsigned buffer_line_sz)
{
unsigned ntaps;
/* See how many lines we can buffer in the internal scaler RAM */
ntaps = AVI_SCAL_RAMSZ / (buffer_line_sz * 4);
if (ntaps < 2)
/* We can't scale lines that big! */
BUG();
if (ntaps <= 4)
return AVI_SCAL_NTAPS_4;
if (ntaps <= 8)
return AVI_SCAL_NTAPS_8;
return AVI_SCAL_NTAPS_16;
}
static void avi_scal_calc_cfg(struct avi_scal_cfg* config,
unsigned from_width,
unsigned to_width,
unsigned from_height,
unsigned to_height,
struct avi_dma_pixfmt pixfmt,
int have_planar)
{
int planar = avi_pixfmt_is_planar(pixfmt) &&
have_planar;
enum avi_pixel_packing packing = avi_pixfmt_get_packing(pixfmt);
unsigned bufln_sz;
/* We can't bypass the scaler when it's configured in planar mode even
* if the frame size stays the same: we still have to upscale the chroma
* plane. */
if (unlikely(to_width == from_width &&
to_height == from_height &&
!planar)) {
/* no scaling needed, use bypass config */
memset(config, 0, sizeof(*config));
config->conf = AVI_SCAL_IN_SRC << AVI_SCAL_CONF_OUTSRC_SHIFT;
return;
}
/* The vertical scaler has a line buffer. To optimize memory usage we
* have to make it store the smallest lines. */
if (to_width > from_width) {
/* We are enlarging the picture, put the vertical scaler
* first */
config->conf = AVI_SCAL_IN_SRC << AVI_SCAL_CONF_VERTSRC_SHIFT
| AVI_SCAL_VERT_SRC << AVI_SCAL_CONF_HORIZSRC_SHIFT
| AVI_SCAL_HORIZ_SRC << AVI_SCAL_CONF_OUTSRC_SHIFT;
bufln_sz = from_width;
} else {
/* We are thinning the picture, put the vertical scaler last */
config->conf = AVI_SCAL_IN_SRC << AVI_SCAL_CONF_HORIZSRC_SHIFT
| AVI_SCAL_HORIZ_SRC << AVI_SCAL_CONF_VERTSRC_SHIFT
| AVI_SCAL_VERT_SRC << AVI_SCAL_CONF_OUTSRC_SHIFT;
bufln_sz = to_width;
}
config->size = (to_height - 1) << AVI_SCAL_HEIGHT_SHIFT |
(to_width - 1) << AVI_SCAL_WIDTH_SHIFT;
config->wcfg0 = avi_scal_size2dimcfg(from_width,
to_width,
AVI_SCAL_NTAPS_16);
config->wincr0 = (from_width - config->wcfg0->ntaps) << 8;
config->wincr0 /= max(to_width - 1, 1U);
config->woffset0 = avi_scal_offset(from_width, to_width);
config->hcfg0 = avi_scal_size2dimcfg(from_height,
to_height,
avi_scal_line_taps(bufln_sz));
config->hincr0 = (from_height - config->hcfg0->ntaps) << 8;
config->hincr0 /= max(to_height - 1, 1U);
config->hoffset0 = avi_scal_offset(from_height, to_height);
config->conf |= (!!planar) << AVI_SCAL_CONF_PLANAR_SHIFT;
if (planar) {
switch(packing) {
case AVI_INTERLEAVED_444_PACKING:
case AVI_INTERLEAVED_YUV_422_PACKING:
default:
config->wincr1 = 0;
config->wcfg1 = 0;
config->woffset1 = 0;
config->hincr1 = 0;
config->hcfg1 = 0;
config->hoffset1 = 0;
break;
case AVI_SEMIPLANAR_YUV_420_PACKING:
config->wincr1 = config->wincr0 / 2;
config->wcfg1 = config->wcfg0;
config->woffset1 = config->woffset0;
config->hincr1 = config->hincr0 / 2;
config->hcfg1 = config->hcfg0;
config->hoffset1 = config->hoffset0;
break;
/* NV16 */
case AVI_SEMIPLANAR_YUV_422_PACKING:
config->wincr1 = config->wincr0 / 2;
config->wcfg1 = config->wcfg0;
config->woffset1 = config->woffset0;
config->hincr1 = config->hincr0;
config->hcfg1 = config->hcfg0;
config->hoffset1 = config->hoffset0;
break;
}
} else {
config->wincr1 = 0;
config->wcfg1 = 0;
config->woffset1 = 0;
config->hincr1 = 0;
config->hcfg1 = 0;
config->hoffset1 = 0;
}
}
/**
* avi_scal_bypass() - Configure a scaler node as passthrough
*/
void avi_scal_bypass(struct avi_node const *scaler)
{
unsigned long const base = avi_node_base(scaler);
AVI_WRITE(AVI_SCAL_IN_SRC << AVI_SCAL_CONF_OUTSRC_SHIFT,
base + AVI_SCAL_CONF);
}
EXPORT_SYMBOL(avi_scal_bypass);
/**
* avi_scal_to_cfg() - Compute scaler configuration
* @config: the structure we'll fill with the computed config
* @from_width: frame's original width
* @to_width: width to resize frame to
* @from_height: frame's original height
* @to_height: height to resize frame to
*/
void avi_scal_to_cfg(struct avi_scal_cfg* config,
u16 from_width,
u16 to_width,
u16 from_height,
u16 to_height,
struct avi_dma_pixfmt pixfmt,
int have_planar)
{
avi_scal_calc_cfg(config,
from_width, to_width,
from_height, to_height,
pixfmt,
have_planar);
}
EXPORT_SYMBOL(avi_scal_to_cfg);
/**
* avi_scal_to_interleaced_cfg() - Compute scaler configuration for interleaced
* output.
* @top_config: the structure we'll fill with the computed config for the top
* field
* @bot_config: the structure we'll fill with the computed config for the bottom
* field
* @from_width: frame's original width
* @to_width: width to resize frame to
* @from_height: frame's original height
* @to_height: height to resize frame to (the complete frame size, not just the
* field's).
*/
void avi_scal_to_interleaced_cfg(struct avi_scal_cfg *top_config,
struct avi_scal_cfg *bot_config,
u16 from_width,
u16 to_width,
u16 from_height,
u16 to_height,
struct avi_dma_pixfmt pixfmt,
int have_planar)
{
avi_scal_calc_cfg(top_config,
from_width, to_width,
from_height, roundup(to_height, 2) / 2,
pixfmt,
have_planar);
*bot_config = *top_config;
/* We offset the bottom field by one output line. Incr moves two output
* lines at a time (since we divided to_height by two above) so we can
* use that to speed up the computation. */
bot_config->hoffset0 += bot_config->hincr0 / 2;
bot_config->hoffset1 += bot_config->hincr1 / 2;
}
EXPORT_SYMBOL(avi_scal_to_interleaced_cfg);
/*
* avi_setup_scal() - Apply configuration to scaler.
* @scaler: node pointing to the scaler to setup
* @width: output width after resize operation
* @height: output height after resize operation
* @config: configuration to apply
*/
void avi_scal_setup(struct avi_node const* scaler,
struct avi_scal_cfg const* config)
{
unsigned long const base = avi_node_base(scaler);
/* Setup format and resizing stages. */
AVI_WRITE(config->conf, base + AVI_SCAL_CONF);
/* Setup dimensions sizes. */
AVI_WRITE(config->size, base + AVI_SCAL_SIZEOUT);
/* Setup plane 0. */
avi_setup_scalplane(base + AVI_SCAL_PLANE0,
config->woffset0,
config->wincr0,
config->wcfg0,
config->hoffset0,
config->hincr0,
config->hcfg0);
if (config->wincr1 || config->hincr1)
avi_setup_scalplane(base + AVI_SCAL_PLANE1,
config->woffset1,
config->wincr1,
config->wcfg1,
config->hoffset1,
config->hincr1,
config->hcfg1);
else
/* Otherwise we use the same config for both planes */
avi_setup_scalplane(base + AVI_SCAL_PLANE1,
config->woffset0,
config->wincr0,
config->wcfg0,
config->hoffset0,
config->hincr0,
config->hcfg0);
}
EXPORT_SYMBOL(avi_scal_setup);
void avi_scal_setup_oneshot(struct avi_node const *scaler,
u16 from_width, u16 to_width,
u16 from_height, u16 to_height,
struct avi_dma_pixfmt pixfmt,
int have_planar)
{
struct avi_scal_cfg scalcfg;
avi_scal_to_cfg(&scalcfg,
from_width, to_width,
from_height, to_height,
pixfmt,
have_planar);
avi_scal_setup(scaler, &scalcfg);
}
EXPORT_SYMBOL(avi_scal_setup_oneshot);
void avi_scal_set_field(struct avi_node const *scaler,
u16 from_height,
u16 to_height,
enum avi_field field)
{
/* I'm not sure if this code works for planar but I'm pretty sure the
* rest of the code in this file is broken for planar anyway. */
unsigned long base = avi_node_base(scaler) + AVI_SCAL_PLANE0;
unsigned hoffset;
u32 hincr;
hoffset = avi_scal_offset(from_height, to_height);
if (field == AVI_BOT_FIELD) {
hincr = AVI_READ(base + AVI_SCAL_VERT_DIM + AVI_SCAL_INCR);
/* Offset by one line */
hoffset += hincr / 2;
}
AVI_WRITE(hoffset, base + AVI_SCAL_VERT_DIM + AVI_SCAL_OFFSET);
/* XXX update plan1 hoffset if needed */
}
EXPORT_SYMBOL(avi_scal_set_field);