// 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; } } }