18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Scaler library 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (c) 2013 Texas Instruments Inc. 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * David Griego, <dagriego@biglakesoftware.com> 88c2ecf20Sopenharmony_ci * Dale Farnsworth, <dale@farnsworth.org> 98c2ecf20Sopenharmony_ci * Archit Taneja, <archit@ti.com> 108c2ecf20Sopenharmony_ci */ 118c2ecf20Sopenharmony_ci 128c2ecf20Sopenharmony_ci#include <linux/err.h> 138c2ecf20Sopenharmony_ci#include <linux/io.h> 148c2ecf20Sopenharmony_ci#include <linux/module.h> 158c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 168c2ecf20Sopenharmony_ci#include <linux/slab.h> 178c2ecf20Sopenharmony_ci 188c2ecf20Sopenharmony_ci#include "sc.h" 198c2ecf20Sopenharmony_ci#include "sc_coeff.h" 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_civoid sc_dump_regs(struct sc_data *sc) 228c2ecf20Sopenharmony_ci{ 238c2ecf20Sopenharmony_ci struct device *dev = &sc->pdev->dev; 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_ci#define DUMPREG(r) dev_dbg(dev, "%-35s %08x\n", #r, \ 268c2ecf20Sopenharmony_ci ioread32(sc->base + CFG_##r)) 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_ci dev_dbg(dev, "SC Registers @ %pa:\n", &sc->res->start); 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_ci DUMPREG(SC0); 318c2ecf20Sopenharmony_ci DUMPREG(SC1); 328c2ecf20Sopenharmony_ci DUMPREG(SC2); 338c2ecf20Sopenharmony_ci DUMPREG(SC3); 348c2ecf20Sopenharmony_ci DUMPREG(SC4); 358c2ecf20Sopenharmony_ci DUMPREG(SC5); 368c2ecf20Sopenharmony_ci DUMPREG(SC6); 378c2ecf20Sopenharmony_ci DUMPREG(SC8); 388c2ecf20Sopenharmony_ci DUMPREG(SC9); 398c2ecf20Sopenharmony_ci DUMPREG(SC10); 408c2ecf20Sopenharmony_ci DUMPREG(SC11); 418c2ecf20Sopenharmony_ci DUMPREG(SC12); 428c2ecf20Sopenharmony_ci DUMPREG(SC13); 438c2ecf20Sopenharmony_ci DUMPREG(SC17); 448c2ecf20Sopenharmony_ci DUMPREG(SC18); 458c2ecf20Sopenharmony_ci DUMPREG(SC19); 468c2ecf20Sopenharmony_ci DUMPREG(SC20); 478c2ecf20Sopenharmony_ci DUMPREG(SC21); 488c2ecf20Sopenharmony_ci DUMPREG(SC22); 498c2ecf20Sopenharmony_ci DUMPREG(SC23); 508c2ecf20Sopenharmony_ci DUMPREG(SC24); 518c2ecf20Sopenharmony_ci DUMPREG(SC25); 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_ci#undef DUMPREG 548c2ecf20Sopenharmony_ci} 558c2ecf20Sopenharmony_ciEXPORT_SYMBOL(sc_dump_regs); 568c2ecf20Sopenharmony_ci 578c2ecf20Sopenharmony_ci/* 588c2ecf20Sopenharmony_ci * set the horizontal scaler coefficients according to the ratio of output to 598c2ecf20Sopenharmony_ci * input widths, after accounting for up to two levels of decimation 608c2ecf20Sopenharmony_ci */ 618c2ecf20Sopenharmony_civoid sc_set_hs_coeffs(struct sc_data *sc, void *addr, unsigned int src_w, 628c2ecf20Sopenharmony_ci unsigned int dst_w) 638c2ecf20Sopenharmony_ci{ 648c2ecf20Sopenharmony_ci int sixteenths; 658c2ecf20Sopenharmony_ci int idx; 668c2ecf20Sopenharmony_ci int i, j; 678c2ecf20Sopenharmony_ci u16 *coeff_h = addr; 688c2ecf20Sopenharmony_ci const u16 *cp; 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_ci if (dst_w > src_w) { 718c2ecf20Sopenharmony_ci idx = HS_UP_SCALE; 728c2ecf20Sopenharmony_ci } else { 738c2ecf20Sopenharmony_ci if ((dst_w << 1) < src_w) 748c2ecf20Sopenharmony_ci dst_w <<= 1; /* first level decimation */ 758c2ecf20Sopenharmony_ci if ((dst_w << 1) < src_w) 768c2ecf20Sopenharmony_ci dst_w <<= 1; /* second level decimation */ 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_ci if (dst_w == src_w) { 798c2ecf20Sopenharmony_ci idx = HS_LE_16_16_SCALE; 808c2ecf20Sopenharmony_ci } else { 818c2ecf20Sopenharmony_ci sixteenths = (dst_w << 4) / src_w; 828c2ecf20Sopenharmony_ci if (sixteenths < 8) 838c2ecf20Sopenharmony_ci sixteenths = 8; 848c2ecf20Sopenharmony_ci idx = HS_LT_9_16_SCALE + sixteenths - 8; 858c2ecf20Sopenharmony_ci } 868c2ecf20Sopenharmony_ci } 878c2ecf20Sopenharmony_ci 888c2ecf20Sopenharmony_ci cp = scaler_hs_coeffs[idx]; 898c2ecf20Sopenharmony_ci 908c2ecf20Sopenharmony_ci for (i = 0; i < SC_NUM_PHASES * 2; i++) { 918c2ecf20Sopenharmony_ci for (j = 0; j < SC_H_NUM_TAPS; j++) 928c2ecf20Sopenharmony_ci *coeff_h++ = *cp++; 938c2ecf20Sopenharmony_ci /* 948c2ecf20Sopenharmony_ci * for each phase, the scaler expects space for 8 coefficients 958c2ecf20Sopenharmony_ci * in it's memory. For the horizontal scaler, we copy the first 968c2ecf20Sopenharmony_ci * 7 coefficients and skip the last slot to move to the next 978c2ecf20Sopenharmony_ci * row to hold coefficients for the next phase 988c2ecf20Sopenharmony_ci */ 998c2ecf20Sopenharmony_ci coeff_h += SC_NUM_TAPS_MEM_ALIGN - SC_H_NUM_TAPS; 1008c2ecf20Sopenharmony_ci } 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_ci sc->load_coeff_h = true; 1038c2ecf20Sopenharmony_ci} 1048c2ecf20Sopenharmony_ciEXPORT_SYMBOL(sc_set_hs_coeffs); 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_ci/* 1078c2ecf20Sopenharmony_ci * set the vertical scaler coefficients according to the ratio of output to 1088c2ecf20Sopenharmony_ci * input heights 1098c2ecf20Sopenharmony_ci */ 1108c2ecf20Sopenharmony_civoid sc_set_vs_coeffs(struct sc_data *sc, void *addr, unsigned int src_h, 1118c2ecf20Sopenharmony_ci unsigned int dst_h) 1128c2ecf20Sopenharmony_ci{ 1138c2ecf20Sopenharmony_ci int sixteenths; 1148c2ecf20Sopenharmony_ci int idx; 1158c2ecf20Sopenharmony_ci int i, j; 1168c2ecf20Sopenharmony_ci u16 *coeff_v = addr; 1178c2ecf20Sopenharmony_ci const u16 *cp; 1188c2ecf20Sopenharmony_ci 1198c2ecf20Sopenharmony_ci if (dst_h > src_h) { 1208c2ecf20Sopenharmony_ci idx = VS_UP_SCALE; 1218c2ecf20Sopenharmony_ci } else if (dst_h == src_h) { 1228c2ecf20Sopenharmony_ci idx = VS_1_TO_1_SCALE; 1238c2ecf20Sopenharmony_ci } else { 1248c2ecf20Sopenharmony_ci sixteenths = (dst_h << 4) / src_h; 1258c2ecf20Sopenharmony_ci if (sixteenths < 8) 1268c2ecf20Sopenharmony_ci sixteenths = 8; 1278c2ecf20Sopenharmony_ci idx = VS_LT_9_16_SCALE + sixteenths - 8; 1288c2ecf20Sopenharmony_ci } 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_ci cp = scaler_vs_coeffs[idx]; 1318c2ecf20Sopenharmony_ci 1328c2ecf20Sopenharmony_ci for (i = 0; i < SC_NUM_PHASES * 2; i++) { 1338c2ecf20Sopenharmony_ci for (j = 0; j < SC_V_NUM_TAPS; j++) 1348c2ecf20Sopenharmony_ci *coeff_v++ = *cp++; 1358c2ecf20Sopenharmony_ci /* 1368c2ecf20Sopenharmony_ci * for the vertical scaler, we copy the first 5 coefficients and 1378c2ecf20Sopenharmony_ci * skip the last 3 slots to move to the next row to hold 1388c2ecf20Sopenharmony_ci * coefficients for the next phase 1398c2ecf20Sopenharmony_ci */ 1408c2ecf20Sopenharmony_ci coeff_v += SC_NUM_TAPS_MEM_ALIGN - SC_V_NUM_TAPS; 1418c2ecf20Sopenharmony_ci } 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_ci sc->load_coeff_v = true; 1448c2ecf20Sopenharmony_ci} 1458c2ecf20Sopenharmony_ciEXPORT_SYMBOL(sc_set_vs_coeffs); 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_civoid sc_config_scaler(struct sc_data *sc, u32 *sc_reg0, u32 *sc_reg8, 1488c2ecf20Sopenharmony_ci u32 *sc_reg17, unsigned int src_w, unsigned int src_h, 1498c2ecf20Sopenharmony_ci unsigned int dst_w, unsigned int dst_h) 1508c2ecf20Sopenharmony_ci{ 1518c2ecf20Sopenharmony_ci struct device *dev = &sc->pdev->dev; 1528c2ecf20Sopenharmony_ci u32 val; 1538c2ecf20Sopenharmony_ci int dcm_x, dcm_shift; 1548c2ecf20Sopenharmony_ci bool use_rav; 1558c2ecf20Sopenharmony_ci unsigned long lltmp; 1568c2ecf20Sopenharmony_ci u32 lin_acc_inc, lin_acc_inc_u; 1578c2ecf20Sopenharmony_ci u32 col_acc_offset; 1588c2ecf20Sopenharmony_ci u16 factor = 0; 1598c2ecf20Sopenharmony_ci int row_acc_init_rav = 0, row_acc_init_rav_b = 0; 1608c2ecf20Sopenharmony_ci u32 row_acc_inc = 0, row_acc_offset = 0, row_acc_offset_b = 0; 1618c2ecf20Sopenharmony_ci /* 1628c2ecf20Sopenharmony_ci * location of SC register in payload memory with respect to the first 1638c2ecf20Sopenharmony_ci * register in the mmr address data block 1648c2ecf20Sopenharmony_ci */ 1658c2ecf20Sopenharmony_ci u32 *sc_reg9 = sc_reg8 + 1; 1668c2ecf20Sopenharmony_ci u32 *sc_reg12 = sc_reg8 + 4; 1678c2ecf20Sopenharmony_ci u32 *sc_reg13 = sc_reg8 + 5; 1688c2ecf20Sopenharmony_ci u32 *sc_reg24 = sc_reg17 + 7; 1698c2ecf20Sopenharmony_ci 1708c2ecf20Sopenharmony_ci val = sc_reg0[0]; 1718c2ecf20Sopenharmony_ci 1728c2ecf20Sopenharmony_ci /* clear all the features(they may get enabled elsewhere later) */ 1738c2ecf20Sopenharmony_ci val &= ~(CFG_SELFGEN_FID | CFG_TRIM | CFG_ENABLE_SIN2_VER_INTP | 1748c2ecf20Sopenharmony_ci CFG_INTERLACE_I | CFG_DCM_4X | CFG_DCM_2X | CFG_AUTO_HS | 1758c2ecf20Sopenharmony_ci CFG_ENABLE_EV | CFG_USE_RAV | CFG_INVT_FID | CFG_SC_BYPASS | 1768c2ecf20Sopenharmony_ci CFG_INTERLACE_O | CFG_Y_PK_EN | CFG_HP_BYPASS | CFG_LINEAR); 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_ci if (src_w == dst_w && src_h == dst_h) { 1798c2ecf20Sopenharmony_ci val |= CFG_SC_BYPASS; 1808c2ecf20Sopenharmony_ci sc_reg0[0] = val; 1818c2ecf20Sopenharmony_ci return; 1828c2ecf20Sopenharmony_ci } 1838c2ecf20Sopenharmony_ci 1848c2ecf20Sopenharmony_ci /* we only support linear scaling for now */ 1858c2ecf20Sopenharmony_ci val |= CFG_LINEAR; 1868c2ecf20Sopenharmony_ci 1878c2ecf20Sopenharmony_ci /* configure horizontal scaler */ 1888c2ecf20Sopenharmony_ci 1898c2ecf20Sopenharmony_ci /* enable 2X or 4X decimation */ 1908c2ecf20Sopenharmony_ci dcm_x = src_w / dst_w; 1918c2ecf20Sopenharmony_ci if (dcm_x > 4) { 1928c2ecf20Sopenharmony_ci val |= CFG_DCM_4X; 1938c2ecf20Sopenharmony_ci dcm_shift = 2; 1948c2ecf20Sopenharmony_ci } else if (dcm_x > 2) { 1958c2ecf20Sopenharmony_ci val |= CFG_DCM_2X; 1968c2ecf20Sopenharmony_ci dcm_shift = 1; 1978c2ecf20Sopenharmony_ci } else { 1988c2ecf20Sopenharmony_ci dcm_shift = 0; 1998c2ecf20Sopenharmony_ci } 2008c2ecf20Sopenharmony_ci 2018c2ecf20Sopenharmony_ci lltmp = dst_w - 1; 2028c2ecf20Sopenharmony_ci lin_acc_inc = div64_u64(((u64)(src_w >> dcm_shift) - 1) << 24, lltmp); 2038c2ecf20Sopenharmony_ci lin_acc_inc_u = 0; 2048c2ecf20Sopenharmony_ci col_acc_offset = 0; 2058c2ecf20Sopenharmony_ci 2068c2ecf20Sopenharmony_ci dev_dbg(dev, "hs config: src_w = %d, dst_w = %d, decimation = %s, lin_acc_inc = %08x\n", 2078c2ecf20Sopenharmony_ci src_w, dst_w, dcm_shift == 2 ? "4x" : 2088c2ecf20Sopenharmony_ci (dcm_shift == 1 ? "2x" : "none"), lin_acc_inc); 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_ci /* configure vertical scaler */ 2118c2ecf20Sopenharmony_ci 2128c2ecf20Sopenharmony_ci /* use RAV for vertical scaler if vertical downscaling is > 4x */ 2138c2ecf20Sopenharmony_ci if (dst_h < (src_h >> 2)) { 2148c2ecf20Sopenharmony_ci use_rav = true; 2158c2ecf20Sopenharmony_ci val |= CFG_USE_RAV; 2168c2ecf20Sopenharmony_ci } else { 2178c2ecf20Sopenharmony_ci use_rav = false; 2188c2ecf20Sopenharmony_ci } 2198c2ecf20Sopenharmony_ci 2208c2ecf20Sopenharmony_ci if (use_rav) { 2218c2ecf20Sopenharmony_ci /* use RAV */ 2228c2ecf20Sopenharmony_ci factor = (u16) ((dst_h << 10) / src_h); 2238c2ecf20Sopenharmony_ci 2248c2ecf20Sopenharmony_ci row_acc_init_rav = factor + ((1 + factor) >> 1); 2258c2ecf20Sopenharmony_ci if (row_acc_init_rav >= 1024) 2268c2ecf20Sopenharmony_ci row_acc_init_rav -= 1024; 2278c2ecf20Sopenharmony_ci 2288c2ecf20Sopenharmony_ci row_acc_init_rav_b = row_acc_init_rav + 2298c2ecf20Sopenharmony_ci (1 + (row_acc_init_rav >> 1)) - 2308c2ecf20Sopenharmony_ci (1024 >> 1); 2318c2ecf20Sopenharmony_ci 2328c2ecf20Sopenharmony_ci if (row_acc_init_rav_b < 0) { 2338c2ecf20Sopenharmony_ci row_acc_init_rav_b += row_acc_init_rav; 2348c2ecf20Sopenharmony_ci row_acc_init_rav *= 2; 2358c2ecf20Sopenharmony_ci } 2368c2ecf20Sopenharmony_ci 2378c2ecf20Sopenharmony_ci dev_dbg(dev, "vs config(RAV): src_h = %d, dst_h = %d, factor = %d, acc_init = %08x, acc_init_b = %08x\n", 2388c2ecf20Sopenharmony_ci src_h, dst_h, factor, row_acc_init_rav, 2398c2ecf20Sopenharmony_ci row_acc_init_rav_b); 2408c2ecf20Sopenharmony_ci } else { 2418c2ecf20Sopenharmony_ci /* use polyphase */ 2428c2ecf20Sopenharmony_ci row_acc_inc = ((src_h - 1) << 16) / (dst_h - 1); 2438c2ecf20Sopenharmony_ci row_acc_offset = 0; 2448c2ecf20Sopenharmony_ci row_acc_offset_b = 0; 2458c2ecf20Sopenharmony_ci 2468c2ecf20Sopenharmony_ci dev_dbg(dev, "vs config(POLY): src_h = %d, dst_h = %d,row_acc_inc = %08x\n", 2478c2ecf20Sopenharmony_ci src_h, dst_h, row_acc_inc); 2488c2ecf20Sopenharmony_ci } 2498c2ecf20Sopenharmony_ci 2508c2ecf20Sopenharmony_ci 2518c2ecf20Sopenharmony_ci sc_reg0[0] = val; 2528c2ecf20Sopenharmony_ci sc_reg0[1] = row_acc_inc; 2538c2ecf20Sopenharmony_ci sc_reg0[2] = row_acc_offset; 2548c2ecf20Sopenharmony_ci sc_reg0[3] = row_acc_offset_b; 2558c2ecf20Sopenharmony_ci 2568c2ecf20Sopenharmony_ci sc_reg0[4] = ((lin_acc_inc_u & CFG_LIN_ACC_INC_U_MASK) << 2578c2ecf20Sopenharmony_ci CFG_LIN_ACC_INC_U_SHIFT) | (dst_w << CFG_TAR_W_SHIFT) | 2588c2ecf20Sopenharmony_ci (dst_h << CFG_TAR_H_SHIFT); 2598c2ecf20Sopenharmony_ci 2608c2ecf20Sopenharmony_ci sc_reg0[5] = (src_w << CFG_SRC_W_SHIFT) | (src_h << CFG_SRC_H_SHIFT); 2618c2ecf20Sopenharmony_ci 2628c2ecf20Sopenharmony_ci sc_reg0[6] = (row_acc_init_rav_b << CFG_ROW_ACC_INIT_RAV_B_SHIFT) | 2638c2ecf20Sopenharmony_ci (row_acc_init_rav << CFG_ROW_ACC_INIT_RAV_SHIFT); 2648c2ecf20Sopenharmony_ci 2658c2ecf20Sopenharmony_ci *sc_reg9 = lin_acc_inc; 2668c2ecf20Sopenharmony_ci 2678c2ecf20Sopenharmony_ci *sc_reg12 = col_acc_offset << CFG_COL_ACC_OFFSET_SHIFT; 2688c2ecf20Sopenharmony_ci 2698c2ecf20Sopenharmony_ci *sc_reg13 = factor; 2708c2ecf20Sopenharmony_ci 2718c2ecf20Sopenharmony_ci *sc_reg24 = (src_w << CFG_ORG_W_SHIFT) | (src_h << CFG_ORG_H_SHIFT); 2728c2ecf20Sopenharmony_ci} 2738c2ecf20Sopenharmony_ciEXPORT_SYMBOL(sc_config_scaler); 2748c2ecf20Sopenharmony_ci 2758c2ecf20Sopenharmony_cistruct sc_data *sc_create(struct platform_device *pdev, const char *res_name) 2768c2ecf20Sopenharmony_ci{ 2778c2ecf20Sopenharmony_ci struct sc_data *sc; 2788c2ecf20Sopenharmony_ci 2798c2ecf20Sopenharmony_ci dev_dbg(&pdev->dev, "sc_create\n"); 2808c2ecf20Sopenharmony_ci 2818c2ecf20Sopenharmony_ci sc = devm_kzalloc(&pdev->dev, sizeof(*sc), GFP_KERNEL); 2828c2ecf20Sopenharmony_ci if (!sc) { 2838c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "couldn't alloc sc_data\n"); 2848c2ecf20Sopenharmony_ci return ERR_PTR(-ENOMEM); 2858c2ecf20Sopenharmony_ci } 2868c2ecf20Sopenharmony_ci 2878c2ecf20Sopenharmony_ci sc->pdev = pdev; 2888c2ecf20Sopenharmony_ci 2898c2ecf20Sopenharmony_ci sc->res = platform_get_resource_byname(pdev, IORESOURCE_MEM, res_name); 2908c2ecf20Sopenharmony_ci if (!sc->res) { 2918c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "missing '%s' platform resources data\n", 2928c2ecf20Sopenharmony_ci res_name); 2938c2ecf20Sopenharmony_ci return ERR_PTR(-ENODEV); 2948c2ecf20Sopenharmony_ci } 2958c2ecf20Sopenharmony_ci 2968c2ecf20Sopenharmony_ci sc->base = devm_ioremap_resource(&pdev->dev, sc->res); 2978c2ecf20Sopenharmony_ci if (IS_ERR(sc->base)) { 2988c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "failed to ioremap\n"); 2998c2ecf20Sopenharmony_ci return ERR_CAST(sc->base); 3008c2ecf20Sopenharmony_ci } 3018c2ecf20Sopenharmony_ci 3028c2ecf20Sopenharmony_ci return sc; 3038c2ecf20Sopenharmony_ci} 3048c2ecf20Sopenharmony_ciEXPORT_SYMBOL(sc_create); 3058c2ecf20Sopenharmony_ci 3068c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("TI VIP/VPE Scaler"); 3078c2ecf20Sopenharmony_ciMODULE_AUTHOR("Texas Instruments Inc."); 3088c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 309