856 lines
23 KiB
C
856 lines
23 KiB
C
|
// SPDX-License-Identifier: GPL-2.0
|
||
|
/*
|
||
|
* Copyright 2019 NXP.
|
||
|
*
|
||
|
* Scaling algorithms were contributed by Dzung Hoang <dzung.hoang@nxp.com>
|
||
|
*/
|
||
|
|
||
|
#include <linux/device.h>
|
||
|
#include <linux/slab.h>
|
||
|
|
||
|
#include "dcss-dev.h"
|
||
|
|
||
|
#define DCSS_SCALER_CTRL 0x00
|
||
|
#define SCALER_EN BIT(0)
|
||
|
#define REPEAT_EN BIT(4)
|
||
|
#define SCALE2MEM_EN BIT(8)
|
||
|
#define MEM2OFIFO_EN BIT(12)
|
||
|
#define DCSS_SCALER_OFIFO_CTRL 0x04
|
||
|
#define OFIFO_LOW_THRES_POS 0
|
||
|
#define OFIFO_LOW_THRES_MASK GENMASK(9, 0)
|
||
|
#define OFIFO_HIGH_THRES_POS 16
|
||
|
#define OFIFO_HIGH_THRES_MASK GENMASK(25, 16)
|
||
|
#define UNDERRUN_DETECT_CLR BIT(26)
|
||
|
#define LOW_THRES_DETECT_CLR BIT(27)
|
||
|
#define HIGH_THRES_DETECT_CLR BIT(28)
|
||
|
#define UNDERRUN_DETECT_EN BIT(29)
|
||
|
#define LOW_THRES_DETECT_EN BIT(30)
|
||
|
#define HIGH_THRES_DETECT_EN BIT(31)
|
||
|
#define DCSS_SCALER_SDATA_CTRL 0x08
|
||
|
#define YUV_EN BIT(0)
|
||
|
#define RTRAM_8LINES BIT(1)
|
||
|
#define Y_UV_BYTE_SWAP BIT(4)
|
||
|
#define A2R10G10B10_FORMAT_POS 8
|
||
|
#define A2R10G10B10_FORMAT_MASK GENMASK(11, 8)
|
||
|
#define DCSS_SCALER_BIT_DEPTH 0x0C
|
||
|
#define LUM_BIT_DEPTH_POS 0
|
||
|
#define LUM_BIT_DEPTH_MASK GENMASK(1, 0)
|
||
|
#define CHR_BIT_DEPTH_POS 4
|
||
|
#define CHR_BIT_DEPTH_MASK GENMASK(5, 4)
|
||
|
#define DCSS_SCALER_SRC_FORMAT 0x10
|
||
|
#define DCSS_SCALER_DST_FORMAT 0x14
|
||
|
#define FORMAT_MASK GENMASK(1, 0)
|
||
|
#define DCSS_SCALER_SRC_LUM_RES 0x18
|
||
|
#define DCSS_SCALER_SRC_CHR_RES 0x1C
|
||
|
#define DCSS_SCALER_DST_LUM_RES 0x20
|
||
|
#define DCSS_SCALER_DST_CHR_RES 0x24
|
||
|
#define WIDTH_POS 0
|
||
|
#define WIDTH_MASK GENMASK(11, 0)
|
||
|
#define HEIGHT_POS 16
|
||
|
#define HEIGHT_MASK GENMASK(27, 16)
|
||
|
#define DCSS_SCALER_V_LUM_START 0x48
|
||
|
#define V_START_MASK GENMASK(15, 0)
|
||
|
#define DCSS_SCALER_V_LUM_INC 0x4C
|
||
|
#define V_INC_MASK GENMASK(15, 0)
|
||
|
#define DCSS_SCALER_H_LUM_START 0x50
|
||
|
#define H_START_MASK GENMASK(18, 0)
|
||
|
#define DCSS_SCALER_H_LUM_INC 0x54
|
||
|
#define H_INC_MASK GENMASK(15, 0)
|
||
|
#define DCSS_SCALER_V_CHR_START 0x58
|
||
|
#define DCSS_SCALER_V_CHR_INC 0x5C
|
||
|
#define DCSS_SCALER_H_CHR_START 0x60
|
||
|
#define DCSS_SCALER_H_CHR_INC 0x64
|
||
|
#define DCSS_SCALER_COEF_VLUM 0x80
|
||
|
#define DCSS_SCALER_COEF_HLUM 0x140
|
||
|
#define DCSS_SCALER_COEF_VCHR 0x200
|
||
|
#define DCSS_SCALER_COEF_HCHR 0x300
|
||
|
|
||
|
struct dcss_scaler_ch {
|
||
|
void __iomem *base_reg;
|
||
|
u32 base_ofs;
|
||
|
struct dcss_scaler *scl;
|
||
|
|
||
|
u32 sdata_ctrl;
|
||
|
u32 scaler_ctrl;
|
||
|
|
||
|
bool scaler_ctrl_chgd;
|
||
|
|
||
|
u32 c_vstart;
|
||
|
u32 c_hstart;
|
||
|
|
||
|
bool use_nn_interpolation;
|
||
|
};
|
||
|
|
||
|
struct dcss_scaler {
|
||
|
struct device *dev;
|
||
|
|
||
|
struct dcss_ctxld *ctxld;
|
||
|
u32 ctx_id;
|
||
|
|
||
|
struct dcss_scaler_ch ch[3];
|
||
|
};
|
||
|
|
||
|
/* scaler coefficients generator */
|
||
|
#define PSC_FRAC_BITS 30
|
||
|
#define PSC_FRAC_SCALE BIT(PSC_FRAC_BITS)
|
||
|
#define PSC_BITS_FOR_PHASE 4
|
||
|
#define PSC_NUM_PHASES 16
|
||
|
#define PSC_STORED_PHASES (PSC_NUM_PHASES / 2 + 1)
|
||
|
#define PSC_NUM_TAPS 7
|
||
|
#define PSC_NUM_TAPS_RGBA 5
|
||
|
#define PSC_COEFF_PRECISION 10
|
||
|
#define PSC_PHASE_FRACTION_BITS 13
|
||
|
#define PSC_PHASE_MASK (PSC_NUM_PHASES - 1)
|
||
|
#define PSC_Q_FRACTION 19
|
||
|
#define PSC_Q_ROUND_OFFSET (1 << (PSC_Q_FRACTION - 1))
|
||
|
|
||
|
/**
|
||
|
* mult_q() - Performs fixed-point multiplication.
|
||
|
* @A: multiplier
|
||
|
* @B: multiplicand
|
||
|
*/
|
||
|
static int mult_q(int A, int B)
|
||
|
{
|
||
|
int result;
|
||
|
s64 temp;
|
||
|
|
||
|
temp = (int64_t)A * (int64_t)B;
|
||
|
temp += PSC_Q_ROUND_OFFSET;
|
||
|
result = (int)(temp >> PSC_Q_FRACTION);
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* div_q() - Performs fixed-point division.
|
||
|
* @A: dividend
|
||
|
* @B: divisor
|
||
|
*/
|
||
|
static int div_q(int A, int B)
|
||
|
{
|
||
|
int result;
|
||
|
s64 temp;
|
||
|
|
||
|
temp = (int64_t)A << PSC_Q_FRACTION;
|
||
|
if ((temp >= 0 && B >= 0) || (temp < 0 && B < 0))
|
||
|
temp += B / 2;
|
||
|
else
|
||
|
temp -= B / 2;
|
||
|
|
||
|
result = (int)(temp / B);
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* exp_approx_q() - Compute approximation to exp(x) function using Taylor
|
||
|
* series.
|
||
|
* @x: fixed-point argument of exp function
|
||
|
*/
|
||
|
static int exp_approx_q(int x)
|
||
|
{
|
||
|
int sum = 1 << PSC_Q_FRACTION;
|
||
|
int term = 1 << PSC_Q_FRACTION;
|
||
|
|
||
|
term = mult_q(term, div_q(x, 1 << PSC_Q_FRACTION));
|
||
|
sum += term;
|
||
|
term = mult_q(term, div_q(x, 2 << PSC_Q_FRACTION));
|
||
|
sum += term;
|
||
|
term = mult_q(term, div_q(x, 3 << PSC_Q_FRACTION));
|
||
|
sum += term;
|
||
|
term = mult_q(term, div_q(x, 4 << PSC_Q_FRACTION));
|
||
|
sum += term;
|
||
|
|
||
|
return sum;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* dcss_scaler_gaussian_filter() - Generate gaussian prototype filter.
|
||
|
* @fc_q: fixed-point cutoff frequency normalized to range [0, 1]
|
||
|
* @use_5_taps: indicates whether to use 5 taps or 7 taps
|
||
|
* @coef: output filter coefficients
|
||
|
*/
|
||
|
static void dcss_scaler_gaussian_filter(int fc_q, bool use_5_taps,
|
||
|
bool phase0_identity,
|
||
|
int coef[][PSC_NUM_TAPS])
|
||
|
{
|
||
|
int sigma_q, g0_q, g1_q, g2_q;
|
||
|
int tap_cnt1, tap_cnt2, tap_idx, phase_cnt;
|
||
|
int mid;
|
||
|
int phase;
|
||
|
int i;
|
||
|
int taps;
|
||
|
|
||
|
if (use_5_taps)
|
||
|
for (phase = 0; phase < PSC_STORED_PHASES; phase++) {
|
||
|
coef[phase][0] = 0;
|
||
|
coef[phase][PSC_NUM_TAPS - 1] = 0;
|
||
|
}
|
||
|
|
||
|
/* seed coefficient scanner */
|
||
|
taps = use_5_taps ? PSC_NUM_TAPS_RGBA : PSC_NUM_TAPS;
|
||
|
mid = (PSC_NUM_PHASES * taps) / 2 - 1;
|
||
|
phase_cnt = (PSC_NUM_PHASES * (PSC_NUM_TAPS + 1)) / 2;
|
||
|
tap_cnt1 = (PSC_NUM_PHASES * PSC_NUM_TAPS) / 2;
|
||
|
tap_cnt2 = (PSC_NUM_PHASES * PSC_NUM_TAPS) / 2;
|
||
|
|
||
|
/* seed gaussian filter generator */
|
||
|
sigma_q = div_q(PSC_Q_ROUND_OFFSET, fc_q);
|
||
|
g0_q = 1 << PSC_Q_FRACTION;
|
||
|
g1_q = exp_approx_q(div_q(-PSC_Q_ROUND_OFFSET,
|
||
|
mult_q(sigma_q, sigma_q)));
|
||
|
g2_q = mult_q(g1_q, g1_q);
|
||
|
coef[phase_cnt & PSC_PHASE_MASK][tap_cnt1 >> PSC_BITS_FOR_PHASE] = g0_q;
|
||
|
|
||
|
for (i = 0; i < mid; i++) {
|
||
|
phase_cnt++;
|
||
|
tap_cnt1--;
|
||
|
tap_cnt2++;
|
||
|
|
||
|
g0_q = mult_q(g0_q, g1_q);
|
||
|
g1_q = mult_q(g1_q, g2_q);
|
||
|
|
||
|
if ((phase_cnt & PSC_PHASE_MASK) <= 8) {
|
||
|
tap_idx = tap_cnt1 >> PSC_BITS_FOR_PHASE;
|
||
|
coef[phase_cnt & PSC_PHASE_MASK][tap_idx] = g0_q;
|
||
|
}
|
||
|
if (((-phase_cnt) & PSC_PHASE_MASK) <= 8) {
|
||
|
tap_idx = tap_cnt2 >> PSC_BITS_FOR_PHASE;
|
||
|
coef[(-phase_cnt) & PSC_PHASE_MASK][tap_idx] = g0_q;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
phase_cnt++;
|
||
|
tap_cnt1--;
|
||
|
coef[phase_cnt & PSC_PHASE_MASK][tap_cnt1 >> PSC_BITS_FOR_PHASE] = 0;
|
||
|
|
||
|
/* override phase 0 with identity filter if specified */
|
||
|
if (phase0_identity)
|
||
|
for (i = 0; i < PSC_NUM_TAPS; i++)
|
||
|
coef[0][i] = i == (PSC_NUM_TAPS >> 1) ?
|
||
|
(1 << PSC_COEFF_PRECISION) : 0;
|
||
|
|
||
|
/* normalize coef */
|
||
|
for (phase = 0; phase < PSC_STORED_PHASES; phase++) {
|
||
|
int sum = 0;
|
||
|
s64 ll_temp;
|
||
|
|
||
|
for (i = 0; i < PSC_NUM_TAPS; i++)
|
||
|
sum += coef[phase][i];
|
||
|
for (i = 0; i < PSC_NUM_TAPS; i++) {
|
||
|
ll_temp = coef[phase][i];
|
||
|
ll_temp <<= PSC_COEFF_PRECISION;
|
||
|
ll_temp += sum >> 1;
|
||
|
ll_temp /= sum;
|
||
|
coef[phase][i] = (int)ll_temp;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void dcss_scaler_nearest_neighbor_filter(bool use_5_taps,
|
||
|
int coef[][PSC_NUM_TAPS])
|
||
|
{
|
||
|
int i, j;
|
||
|
|
||
|
for (i = 0; i < PSC_STORED_PHASES; i++)
|
||
|
for (j = 0; j < PSC_NUM_TAPS; j++)
|
||
|
coef[i][j] = j == PSC_NUM_TAPS >> 1 ?
|
||
|
(1 << PSC_COEFF_PRECISION) : 0;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* dcss_scaler_filter_design() - Compute filter coefficients using
|
||
|
* Gaussian filter.
|
||
|
* @src_length: length of input
|
||
|
* @dst_length: length of output
|
||
|
* @use_5_taps: 0 for 7 taps per phase, 1 for 5 taps
|
||
|
* @coef: output coefficients
|
||
|
*/
|
||
|
static void dcss_scaler_filter_design(int src_length, int dst_length,
|
||
|
bool use_5_taps, bool phase0_identity,
|
||
|
int coef[][PSC_NUM_TAPS],
|
||
|
bool nn_interpolation)
|
||
|
{
|
||
|
int fc_q;
|
||
|
|
||
|
/* compute cutoff frequency */
|
||
|
if (dst_length >= src_length)
|
||
|
fc_q = div_q(1, PSC_NUM_PHASES);
|
||
|
else
|
||
|
fc_q = div_q(dst_length, src_length * PSC_NUM_PHASES);
|
||
|
|
||
|
if (nn_interpolation)
|
||
|
dcss_scaler_nearest_neighbor_filter(use_5_taps, coef);
|
||
|
else
|
||
|
/* compute gaussian filter coefficients */
|
||
|
dcss_scaler_gaussian_filter(fc_q, use_5_taps, phase0_identity, coef);
|
||
|
}
|
||
|
|
||
|
static void dcss_scaler_write(struct dcss_scaler_ch *ch, u32 val, u32 ofs)
|
||
|
{
|
||
|
struct dcss_scaler *scl = ch->scl;
|
||
|
|
||
|
dcss_ctxld_write(scl->ctxld, scl->ctx_id, val, ch->base_ofs + ofs);
|
||
|
}
|
||
|
|
||
|
static int dcss_scaler_ch_init_all(struct dcss_scaler *scl,
|
||
|
unsigned long scaler_base)
|
||
|
{
|
||
|
struct dcss_scaler_ch *ch;
|
||
|
int i;
|
||
|
|
||
|
for (i = 0; i < 3; i++) {
|
||
|
ch = &scl->ch[i];
|
||
|
|
||
|
ch->base_ofs = scaler_base + i * 0x400;
|
||
|
|
||
|
ch->base_reg = ioremap(ch->base_ofs, SZ_4K);
|
||
|
if (!ch->base_reg) {
|
||
|
dev_err(scl->dev, "scaler: unable to remap ch base\n");
|
||
|
return -ENOMEM;
|
||
|
}
|
||
|
|
||
|
ch->scl = scl;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int dcss_scaler_init(struct dcss_dev *dcss, unsigned long scaler_base)
|
||
|
{
|
||
|
struct dcss_scaler *scaler;
|
||
|
|
||
|
scaler = kzalloc(sizeof(*scaler), GFP_KERNEL);
|
||
|
if (!scaler)
|
||
|
return -ENOMEM;
|
||
|
|
||
|
dcss->scaler = scaler;
|
||
|
scaler->dev = dcss->dev;
|
||
|
scaler->ctxld = dcss->ctxld;
|
||
|
scaler->ctx_id = CTX_SB_HP;
|
||
|
|
||
|
if (dcss_scaler_ch_init_all(scaler, scaler_base)) {
|
||
|
int i;
|
||
|
|
||
|
for (i = 0; i < 3; i++) {
|
||
|
if (scaler->ch[i].base_reg)
|
||
|
iounmap(scaler->ch[i].base_reg);
|
||
|
}
|
||
|
|
||
|
kfree(scaler);
|
||
|
|
||
|
return -ENOMEM;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
void dcss_scaler_exit(struct dcss_scaler *scl)
|
||
|
{
|
||
|
int ch_no;
|
||
|
|
||
|
for (ch_no = 0; ch_no < 3; ch_no++) {
|
||
|
struct dcss_scaler_ch *ch = &scl->ch[ch_no];
|
||
|
|
||
|
dcss_writel(0, ch->base_reg + DCSS_SCALER_CTRL);
|
||
|
|
||
|
if (ch->base_reg)
|
||
|
iounmap(ch->base_reg);
|
||
|
}
|
||
|
|
||
|
kfree(scl);
|
||
|
}
|
||
|
|
||
|
void dcss_scaler_ch_enable(struct dcss_scaler *scl, int ch_num, bool en)
|
||
|
{
|
||
|
struct dcss_scaler_ch *ch = &scl->ch[ch_num];
|
||
|
u32 scaler_ctrl;
|
||
|
|
||
|
scaler_ctrl = en ? SCALER_EN | REPEAT_EN : 0;
|
||
|
|
||
|
if (en)
|
||
|
dcss_scaler_write(ch, ch->sdata_ctrl, DCSS_SCALER_SDATA_CTRL);
|
||
|
|
||
|
if (ch->scaler_ctrl != scaler_ctrl)
|
||
|
ch->scaler_ctrl_chgd = true;
|
||
|
|
||
|
ch->scaler_ctrl = scaler_ctrl;
|
||
|
}
|
||
|
|
||
|
static void dcss_scaler_yuv_enable(struct dcss_scaler_ch *ch, bool en)
|
||
|
{
|
||
|
ch->sdata_ctrl &= ~YUV_EN;
|
||
|
ch->sdata_ctrl |= en ? YUV_EN : 0;
|
||
|
}
|
||
|
|
||
|
static void dcss_scaler_rtr_8lines_enable(struct dcss_scaler_ch *ch, bool en)
|
||
|
{
|
||
|
ch->sdata_ctrl &= ~RTRAM_8LINES;
|
||
|
ch->sdata_ctrl |= en ? RTRAM_8LINES : 0;
|
||
|
}
|
||
|
|
||
|
static void dcss_scaler_bit_depth_set(struct dcss_scaler_ch *ch, int depth)
|
||
|
{
|
||
|
u32 val;
|
||
|
|
||
|
val = depth == 30 ? 2 : 0;
|
||
|
|
||
|
dcss_scaler_write(ch,
|
||
|
((val << CHR_BIT_DEPTH_POS) & CHR_BIT_DEPTH_MASK) |
|
||
|
((val << LUM_BIT_DEPTH_POS) & LUM_BIT_DEPTH_MASK),
|
||
|
DCSS_SCALER_BIT_DEPTH);
|
||
|
}
|
||
|
|
||
|
enum buffer_format {
|
||
|
BUF_FMT_YUV420,
|
||
|
BUF_FMT_YUV422,
|
||
|
BUF_FMT_ARGB8888_YUV444,
|
||
|
};
|
||
|
|
||
|
enum chroma_location {
|
||
|
PSC_LOC_HORZ_0_VERT_1_OVER_4 = 0,
|
||
|
PSC_LOC_HORZ_1_OVER_4_VERT_1_OVER_4 = 1,
|
||
|
PSC_LOC_HORZ_0_VERT_0 = 2,
|
||
|
PSC_LOC_HORZ_1_OVER_4_VERT_0 = 3,
|
||
|
PSC_LOC_HORZ_0_VERT_1_OVER_2 = 4,
|
||
|
PSC_LOC_HORZ_1_OVER_4_VERT_1_OVER_2 = 5
|
||
|
};
|
||
|
|
||
|
static void dcss_scaler_format_set(struct dcss_scaler_ch *ch,
|
||
|
enum buffer_format src_fmt,
|
||
|
enum buffer_format dst_fmt)
|
||
|
{
|
||
|
dcss_scaler_write(ch, src_fmt, DCSS_SCALER_SRC_FORMAT);
|
||
|
dcss_scaler_write(ch, dst_fmt, DCSS_SCALER_DST_FORMAT);
|
||
|
}
|
||
|
|
||
|
static void dcss_scaler_res_set(struct dcss_scaler_ch *ch,
|
||
|
int src_xres, int src_yres,
|
||
|
int dst_xres, int dst_yres,
|
||
|
u32 pix_format, enum buffer_format dst_format)
|
||
|
{
|
||
|
u32 lsrc_xres, lsrc_yres, csrc_xres, csrc_yres;
|
||
|
u32 ldst_xres, ldst_yres, cdst_xres, cdst_yres;
|
||
|
bool src_is_444 = true;
|
||
|
|
||
|
lsrc_xres = src_xres;
|
||
|
csrc_xres = src_xres;
|
||
|
lsrc_yres = src_yres;
|
||
|
csrc_yres = src_yres;
|
||
|
ldst_xres = dst_xres;
|
||
|
cdst_xres = dst_xres;
|
||
|
ldst_yres = dst_yres;
|
||
|
cdst_yres = dst_yres;
|
||
|
|
||
|
if (pix_format == DRM_FORMAT_UYVY || pix_format == DRM_FORMAT_VYUY ||
|
||
|
pix_format == DRM_FORMAT_YUYV || pix_format == DRM_FORMAT_YVYU) {
|
||
|
csrc_xres >>= 1;
|
||
|
src_is_444 = false;
|
||
|
} else if (pix_format == DRM_FORMAT_NV12 ||
|
||
|
pix_format == DRM_FORMAT_NV21) {
|
||
|
csrc_xres >>= 1;
|
||
|
csrc_yres >>= 1;
|
||
|
src_is_444 = false;
|
||
|
}
|
||
|
|
||
|
if (dst_format == BUF_FMT_YUV422)
|
||
|
cdst_xres >>= 1;
|
||
|
|
||
|
/* for 4:4:4 to 4:2:2 conversion, source height should be 1 less */
|
||
|
if (src_is_444 && dst_format == BUF_FMT_YUV422) {
|
||
|
lsrc_yres--;
|
||
|
csrc_yres--;
|
||
|
}
|
||
|
|
||
|
dcss_scaler_write(ch, (((lsrc_yres - 1) << HEIGHT_POS) & HEIGHT_MASK) |
|
||
|
(((lsrc_xres - 1) << WIDTH_POS) & WIDTH_MASK),
|
||
|
DCSS_SCALER_SRC_LUM_RES);
|
||
|
dcss_scaler_write(ch, (((csrc_yres - 1) << HEIGHT_POS) & HEIGHT_MASK) |
|
||
|
(((csrc_xres - 1) << WIDTH_POS) & WIDTH_MASK),
|
||
|
DCSS_SCALER_SRC_CHR_RES);
|
||
|
dcss_scaler_write(ch, (((ldst_yres - 1) << HEIGHT_POS) & HEIGHT_MASK) |
|
||
|
(((ldst_xres - 1) << WIDTH_POS) & WIDTH_MASK),
|
||
|
DCSS_SCALER_DST_LUM_RES);
|
||
|
dcss_scaler_write(ch, (((cdst_yres - 1) << HEIGHT_POS) & HEIGHT_MASK) |
|
||
|
(((cdst_xres - 1) << WIDTH_POS) & WIDTH_MASK),
|
||
|
DCSS_SCALER_DST_CHR_RES);
|
||
|
}
|
||
|
|
||
|
#define downscale_fp(factor, fp_pos) ((factor) << (fp_pos))
|
||
|
#define upscale_fp(factor, fp_pos) ((1 << (fp_pos)) / (factor))
|
||
|
|
||
|
struct dcss_scaler_factors {
|
||
|
int downscale;
|
||
|
int upscale;
|
||
|
};
|
||
|
|
||
|
static const struct dcss_scaler_factors dcss_scaler_factors[] = {
|
||
|
{3, 8}, {5, 8}, {5, 8},
|
||
|
};
|
||
|
|
||
|
static void dcss_scaler_fractions_set(struct dcss_scaler_ch *ch,
|
||
|
int src_xres, int src_yres,
|
||
|
int dst_xres, int dst_yres,
|
||
|
u32 src_format, u32 dst_format,
|
||
|
enum chroma_location src_chroma_loc)
|
||
|
{
|
||
|
int src_c_xres, src_c_yres, dst_c_xres, dst_c_yres;
|
||
|
u32 l_vinc, l_hinc, c_vinc, c_hinc;
|
||
|
u32 c_vstart, c_hstart;
|
||
|
|
||
|
src_c_xres = src_xres;
|
||
|
src_c_yres = src_yres;
|
||
|
dst_c_xres = dst_xres;
|
||
|
dst_c_yres = dst_yres;
|
||
|
|
||
|
c_vstart = 0;
|
||
|
c_hstart = 0;
|
||
|
|
||
|
/* adjustments for source chroma location */
|
||
|
if (src_format == BUF_FMT_YUV420) {
|
||
|
/* vertical input chroma position adjustment */
|
||
|
switch (src_chroma_loc) {
|
||
|
case PSC_LOC_HORZ_0_VERT_1_OVER_4:
|
||
|
case PSC_LOC_HORZ_1_OVER_4_VERT_1_OVER_4:
|
||
|
/*
|
||
|
* move chroma up to first luma line
|
||
|
* (1/4 chroma input line spacing)
|
||
|
*/
|
||
|
c_vstart -= (1 << (PSC_PHASE_FRACTION_BITS - 2));
|
||
|
break;
|
||
|
case PSC_LOC_HORZ_0_VERT_1_OVER_2:
|
||
|
case PSC_LOC_HORZ_1_OVER_4_VERT_1_OVER_2:
|
||
|
/*
|
||
|
* move chroma up to first luma line
|
||
|
* (1/2 chroma input line spacing)
|
||
|
*/
|
||
|
c_vstart -= (1 << (PSC_PHASE_FRACTION_BITS - 1));
|
||
|
break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
/* horizontal input chroma position adjustment */
|
||
|
switch (src_chroma_loc) {
|
||
|
case PSC_LOC_HORZ_1_OVER_4_VERT_1_OVER_4:
|
||
|
case PSC_LOC_HORZ_1_OVER_4_VERT_0:
|
||
|
case PSC_LOC_HORZ_1_OVER_4_VERT_1_OVER_2:
|
||
|
/* move chroma left 1/4 chroma input sample spacing */
|
||
|
c_hstart -= (1 << (PSC_PHASE_FRACTION_BITS - 2));
|
||
|
break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* adjustments to chroma resolution */
|
||
|
if (src_format == BUF_FMT_YUV420) {
|
||
|
src_c_xres >>= 1;
|
||
|
src_c_yres >>= 1;
|
||
|
} else if (src_format == BUF_FMT_YUV422) {
|
||
|
src_c_xres >>= 1;
|
||
|
}
|
||
|
|
||
|
if (dst_format == BUF_FMT_YUV422)
|
||
|
dst_c_xres >>= 1;
|
||
|
|
||
|
l_vinc = ((src_yres << 13) + (dst_yres >> 1)) / dst_yres;
|
||
|
c_vinc = ((src_c_yres << 13) + (dst_c_yres >> 1)) / dst_c_yres;
|
||
|
l_hinc = ((src_xres << 13) + (dst_xres >> 1)) / dst_xres;
|
||
|
c_hinc = ((src_c_xres << 13) + (dst_c_xres >> 1)) / dst_c_xres;
|
||
|
|
||
|
/* save chroma start phase */
|
||
|
ch->c_vstart = c_vstart;
|
||
|
ch->c_hstart = c_hstart;
|
||
|
|
||
|
dcss_scaler_write(ch, 0, DCSS_SCALER_V_LUM_START);
|
||
|
dcss_scaler_write(ch, l_vinc, DCSS_SCALER_V_LUM_INC);
|
||
|
|
||
|
dcss_scaler_write(ch, 0, DCSS_SCALER_H_LUM_START);
|
||
|
dcss_scaler_write(ch, l_hinc, DCSS_SCALER_H_LUM_INC);
|
||
|
|
||
|
dcss_scaler_write(ch, c_vstart, DCSS_SCALER_V_CHR_START);
|
||
|
dcss_scaler_write(ch, c_vinc, DCSS_SCALER_V_CHR_INC);
|
||
|
|
||
|
dcss_scaler_write(ch, c_hstart, DCSS_SCALER_H_CHR_START);
|
||
|
dcss_scaler_write(ch, c_hinc, DCSS_SCALER_H_CHR_INC);
|
||
|
}
|
||
|
|
||
|
int dcss_scaler_get_min_max_ratios(struct dcss_scaler *scl, int ch_num,
|
||
|
int *min, int *max)
|
||
|
{
|
||
|
*min = upscale_fp(dcss_scaler_factors[ch_num].upscale, 16);
|
||
|
*max = downscale_fp(dcss_scaler_factors[ch_num].downscale, 16);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void dcss_scaler_program_5_coef_set(struct dcss_scaler_ch *ch,
|
||
|
int base_addr,
|
||
|
int coef[][PSC_NUM_TAPS])
|
||
|
{
|
||
|
int i, phase;
|
||
|
|
||
|
for (i = 0; i < PSC_STORED_PHASES; i++) {
|
||
|
dcss_scaler_write(ch, ((coef[i][1] & 0xfff) << 16 |
|
||
|
(coef[i][2] & 0xfff) << 4 |
|
||
|
(coef[i][3] & 0xf00) >> 8),
|
||
|
base_addr + i * sizeof(u32));
|
||
|
dcss_scaler_write(ch, ((coef[i][3] & 0x0ff) << 20 |
|
||
|
(coef[i][4] & 0xfff) << 8 |
|
||
|
(coef[i][5] & 0xff0) >> 4),
|
||
|
base_addr + 0x40 + i * sizeof(u32));
|
||
|
dcss_scaler_write(ch, ((coef[i][5] & 0x00f) << 24),
|
||
|
base_addr + 0x80 + i * sizeof(u32));
|
||
|
}
|
||
|
|
||
|
/* reverse both phase and tap orderings */
|
||
|
for (phase = (PSC_NUM_PHASES >> 1) - 1;
|
||
|
i < PSC_NUM_PHASES; i++, phase--) {
|
||
|
dcss_scaler_write(ch, ((coef[phase][5] & 0xfff) << 16 |
|
||
|
(coef[phase][4] & 0xfff) << 4 |
|
||
|
(coef[phase][3] & 0xf00) >> 8),
|
||
|
base_addr + i * sizeof(u32));
|
||
|
dcss_scaler_write(ch, ((coef[phase][3] & 0x0ff) << 20 |
|
||
|
(coef[phase][2] & 0xfff) << 8 |
|
||
|
(coef[phase][1] & 0xff0) >> 4),
|
||
|
base_addr + 0x40 + i * sizeof(u32));
|
||
|
dcss_scaler_write(ch, ((coef[phase][1] & 0x00f) << 24),
|
||
|
base_addr + 0x80 + i * sizeof(u32));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void dcss_scaler_program_7_coef_set(struct dcss_scaler_ch *ch,
|
||
|
int base_addr,
|
||
|
int coef[][PSC_NUM_TAPS])
|
||
|
{
|
||
|
int i, phase;
|
||
|
|
||
|
for (i = 0; i < PSC_STORED_PHASES; i++) {
|
||
|
dcss_scaler_write(ch, ((coef[i][0] & 0xfff) << 16 |
|
||
|
(coef[i][1] & 0xfff) << 4 |
|
||
|
(coef[i][2] & 0xf00) >> 8),
|
||
|
base_addr + i * sizeof(u32));
|
||
|
dcss_scaler_write(ch, ((coef[i][2] & 0x0ff) << 20 |
|
||
|
(coef[i][3] & 0xfff) << 8 |
|
||
|
(coef[i][4] & 0xff0) >> 4),
|
||
|
base_addr + 0x40 + i * sizeof(u32));
|
||
|
dcss_scaler_write(ch, ((coef[i][4] & 0x00f) << 24 |
|
||
|
(coef[i][5] & 0xfff) << 12 |
|
||
|
(coef[i][6] & 0xfff)),
|
||
|
base_addr + 0x80 + i * sizeof(u32));
|
||
|
}
|
||
|
|
||
|
/* reverse both phase and tap orderings */
|
||
|
for (phase = (PSC_NUM_PHASES >> 1) - 1;
|
||
|
i < PSC_NUM_PHASES; i++, phase--) {
|
||
|
dcss_scaler_write(ch, ((coef[phase][6] & 0xfff) << 16 |
|
||
|
(coef[phase][5] & 0xfff) << 4 |
|
||
|
(coef[phase][4] & 0xf00) >> 8),
|
||
|
base_addr + i * sizeof(u32));
|
||
|
dcss_scaler_write(ch, ((coef[phase][4] & 0x0ff) << 20 |
|
||
|
(coef[phase][3] & 0xfff) << 8 |
|
||
|
(coef[phase][2] & 0xff0) >> 4),
|
||
|
base_addr + 0x40 + i * sizeof(u32));
|
||
|
dcss_scaler_write(ch, ((coef[phase][2] & 0x00f) << 24 |
|
||
|
(coef[phase][1] & 0xfff) << 12 |
|
||
|
(coef[phase][0] & 0xfff)),
|
||
|
base_addr + 0x80 + i * sizeof(u32));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void dcss_scaler_yuv_coef_set(struct dcss_scaler_ch *ch,
|
||
|
enum buffer_format src_format,
|
||
|
enum buffer_format dst_format,
|
||
|
bool use_5_taps,
|
||
|
int src_xres, int src_yres, int dst_xres,
|
||
|
int dst_yres)
|
||
|
{
|
||
|
int coef[PSC_STORED_PHASES][PSC_NUM_TAPS];
|
||
|
bool program_5_taps = use_5_taps ||
|
||
|
(dst_format == BUF_FMT_YUV422 &&
|
||
|
src_format == BUF_FMT_ARGB8888_YUV444);
|
||
|
|
||
|
/* horizontal luma */
|
||
|
dcss_scaler_filter_design(src_xres, dst_xres, false,
|
||
|
src_xres == dst_xres, coef,
|
||
|
ch->use_nn_interpolation);
|
||
|
dcss_scaler_program_7_coef_set(ch, DCSS_SCALER_COEF_HLUM, coef);
|
||
|
|
||
|
/* vertical luma */
|
||
|
dcss_scaler_filter_design(src_yres, dst_yres, program_5_taps,
|
||
|
src_yres == dst_yres, coef,
|
||
|
ch->use_nn_interpolation);
|
||
|
|
||
|
if (program_5_taps)
|
||
|
dcss_scaler_program_5_coef_set(ch, DCSS_SCALER_COEF_VLUM, coef);
|
||
|
else
|
||
|
dcss_scaler_program_7_coef_set(ch, DCSS_SCALER_COEF_VLUM, coef);
|
||
|
|
||
|
/* adjust chroma resolution */
|
||
|
if (src_format != BUF_FMT_ARGB8888_YUV444)
|
||
|
src_xres >>= 1;
|
||
|
if (src_format == BUF_FMT_YUV420)
|
||
|
src_yres >>= 1;
|
||
|
if (dst_format != BUF_FMT_ARGB8888_YUV444)
|
||
|
dst_xres >>= 1;
|
||
|
if (dst_format == BUF_FMT_YUV420) /* should not happen */
|
||
|
dst_yres >>= 1;
|
||
|
|
||
|
/* horizontal chroma */
|
||
|
dcss_scaler_filter_design(src_xres, dst_xres, false,
|
||
|
(src_xres == dst_xres) && (ch->c_hstart == 0),
|
||
|
coef, ch->use_nn_interpolation);
|
||
|
|
||
|
dcss_scaler_program_7_coef_set(ch, DCSS_SCALER_COEF_HCHR, coef);
|
||
|
|
||
|
/* vertical chroma */
|
||
|
dcss_scaler_filter_design(src_yres, dst_yres, program_5_taps,
|
||
|
(src_yres == dst_yres) && (ch->c_vstart == 0),
|
||
|
coef, ch->use_nn_interpolation);
|
||
|
if (program_5_taps)
|
||
|
dcss_scaler_program_5_coef_set(ch, DCSS_SCALER_COEF_VCHR, coef);
|
||
|
else
|
||
|
dcss_scaler_program_7_coef_set(ch, DCSS_SCALER_COEF_VCHR, coef);
|
||
|
}
|
||
|
|
||
|
static void dcss_scaler_rgb_coef_set(struct dcss_scaler_ch *ch,
|
||
|
int src_xres, int src_yres, int dst_xres,
|
||
|
int dst_yres)
|
||
|
{
|
||
|
int coef[PSC_STORED_PHASES][PSC_NUM_TAPS];
|
||
|
|
||
|
/* horizontal RGB */
|
||
|
dcss_scaler_filter_design(src_xres, dst_xres, false,
|
||
|
src_xres == dst_xres, coef,
|
||
|
ch->use_nn_interpolation);
|
||
|
dcss_scaler_program_7_coef_set(ch, DCSS_SCALER_COEF_HLUM, coef);
|
||
|
|
||
|
/* vertical RGB */
|
||
|
dcss_scaler_filter_design(src_yres, dst_yres, false,
|
||
|
src_yres == dst_yres, coef,
|
||
|
ch->use_nn_interpolation);
|
||
|
dcss_scaler_program_7_coef_set(ch, DCSS_SCALER_COEF_VLUM, coef);
|
||
|
}
|
||
|
|
||
|
static void dcss_scaler_set_rgb10_order(struct dcss_scaler_ch *ch,
|
||
|
const struct drm_format_info *format)
|
||
|
{
|
||
|
u32 a2r10g10b10_format;
|
||
|
|
||
|
if (format->is_yuv)
|
||
|
return;
|
||
|
|
||
|
ch->sdata_ctrl &= ~A2R10G10B10_FORMAT_MASK;
|
||
|
|
||
|
if (format->depth != 30)
|
||
|
return;
|
||
|
|
||
|
switch (format->format) {
|
||
|
case DRM_FORMAT_ARGB2101010:
|
||
|
case DRM_FORMAT_XRGB2101010:
|
||
|
a2r10g10b10_format = 0;
|
||
|
break;
|
||
|
|
||
|
case DRM_FORMAT_ABGR2101010:
|
||
|
case DRM_FORMAT_XBGR2101010:
|
||
|
a2r10g10b10_format = 5;
|
||
|
break;
|
||
|
|
||
|
case DRM_FORMAT_RGBA1010102:
|
||
|
case DRM_FORMAT_RGBX1010102:
|
||
|
a2r10g10b10_format = 6;
|
||
|
break;
|
||
|
|
||
|
case DRM_FORMAT_BGRA1010102:
|
||
|
case DRM_FORMAT_BGRX1010102:
|
||
|
a2r10g10b10_format = 11;
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
a2r10g10b10_format = 0;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
ch->sdata_ctrl |= a2r10g10b10_format << A2R10G10B10_FORMAT_POS;
|
||
|
}
|
||
|
|
||
|
void dcss_scaler_set_filter(struct dcss_scaler *scl, int ch_num,
|
||
|
enum drm_scaling_filter scaling_filter)
|
||
|
{
|
||
|
struct dcss_scaler_ch *ch = &scl->ch[ch_num];
|
||
|
|
||
|
ch->use_nn_interpolation = scaling_filter == DRM_SCALING_FILTER_NEAREST_NEIGHBOR;
|
||
|
}
|
||
|
|
||
|
void dcss_scaler_setup(struct dcss_scaler *scl, int ch_num,
|
||
|
const struct drm_format_info *format,
|
||
|
int src_xres, int src_yres, int dst_xres, int dst_yres,
|
||
|
u32 vrefresh_hz)
|
||
|
{
|
||
|
struct dcss_scaler_ch *ch = &scl->ch[ch_num];
|
||
|
unsigned int pixel_depth = 0;
|
||
|
bool rtr_8line_en = false;
|
||
|
bool use_5_taps = false;
|
||
|
enum buffer_format src_format = BUF_FMT_ARGB8888_YUV444;
|
||
|
enum buffer_format dst_format = BUF_FMT_ARGB8888_YUV444;
|
||
|
u32 pix_format = format->format;
|
||
|
|
||
|
if (format->is_yuv) {
|
||
|
dcss_scaler_yuv_enable(ch, true);
|
||
|
|
||
|
if (pix_format == DRM_FORMAT_NV12 ||
|
||
|
pix_format == DRM_FORMAT_NV21) {
|
||
|
rtr_8line_en = true;
|
||
|
src_format = BUF_FMT_YUV420;
|
||
|
} else if (pix_format == DRM_FORMAT_UYVY ||
|
||
|
pix_format == DRM_FORMAT_VYUY ||
|
||
|
pix_format == DRM_FORMAT_YUYV ||
|
||
|
pix_format == DRM_FORMAT_YVYU) {
|
||
|
src_format = BUF_FMT_YUV422;
|
||
|
}
|
||
|
|
||
|
use_5_taps = !rtr_8line_en;
|
||
|
} else {
|
||
|
dcss_scaler_yuv_enable(ch, false);
|
||
|
|
||
|
pixel_depth = format->depth;
|
||
|
}
|
||
|
|
||
|
dcss_scaler_fractions_set(ch, src_xres, src_yres, dst_xres,
|
||
|
dst_yres, src_format, dst_format,
|
||
|
PSC_LOC_HORZ_0_VERT_1_OVER_4);
|
||
|
|
||
|
if (format->is_yuv)
|
||
|
dcss_scaler_yuv_coef_set(ch, src_format, dst_format,
|
||
|
use_5_taps, src_xres, src_yres,
|
||
|
dst_xres, dst_yres);
|
||
|
else
|
||
|
dcss_scaler_rgb_coef_set(ch, src_xres, src_yres,
|
||
|
dst_xres, dst_yres);
|
||
|
|
||
|
dcss_scaler_rtr_8lines_enable(ch, rtr_8line_en);
|
||
|
dcss_scaler_bit_depth_set(ch, pixel_depth);
|
||
|
dcss_scaler_set_rgb10_order(ch, format);
|
||
|
dcss_scaler_format_set(ch, src_format, dst_format);
|
||
|
dcss_scaler_res_set(ch, src_xres, src_yres, dst_xres, dst_yres,
|
||
|
pix_format, dst_format);
|
||
|
}
|
||
|
|
||
|
/* This function will be called from interrupt context. */
|
||
|
void dcss_scaler_write_sclctrl(struct dcss_scaler *scl)
|
||
|
{
|
||
|
int chnum;
|
||
|
|
||
|
dcss_ctxld_assert_locked(scl->ctxld);
|
||
|
|
||
|
for (chnum = 0; chnum < 3; chnum++) {
|
||
|
struct dcss_scaler_ch *ch = &scl->ch[chnum];
|
||
|
|
||
|
if (ch->scaler_ctrl_chgd) {
|
||
|
dcss_ctxld_write_irqsafe(scl->ctxld, scl->ctx_id,
|
||
|
ch->scaler_ctrl,
|
||
|
ch->base_ofs +
|
||
|
DCSS_SCALER_CTRL);
|
||
|
ch->scaler_ctrl_chgd = false;
|
||
|
}
|
||
|
}
|
||
|
}
|