/* * drivers/video/tegra/dc/nvdisplay/nvdisp.c * * Copyright (c) 2014-2021, NVIDIA CORPORATION, All rights reserved. * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and * may be copied, distributed, and modified under those terms. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include "dc.h" #include "nvdisp.h" #include "nvdisp_priv.h" #include "dc_config.h" #include "dc_priv.h" #include "dp.h" #include "hw_nvdisp_nvdisp.h" #include "hw_win_nvdisp.h" #include "dc_common.h" DEFINE_MUTEX(tegra_nvdisp_lock); static struct nvdisp_common_imp_data g_imp; #define NVDISP_INPUT_LUT_SIZE 257 #define NVDISP_OUTPUT_LUT_SIZE 1025 #define NSEC_PER_MICROSEC 1000 #define MIN_FRAME_INTERVAL 1000 /* Global variables provided for clocks * common to all heads */ struct clk *hubclk; static struct clk *compclk; static bool nvdisp_common_init_done; static struct reset_control *nvdisp_common_rst[DC_N_WINDOWS+1]; static int tegra_nvdisp_set_color_control(struct tegra_dc *dc); /* * As per nvdisplay programming guidelines, only hubclk,dispclk,dscclk, * pclk0 clocks should be enabled before accessing any display register * in head0 power domain. But, as BL is unconditionally unpowergating * all display powerdomains, enabling pclk1 and pclk2 clocks * even here as WAR to avoid display register access issues in kernel * Bug 200343370 tracks issue in BL. */ static struct tegra_dc_pd_clk_info t18x_disp_pd0_clk_info[] = { { .name = "nvdisplayhub", .clk = NULL, }, { .name = "nvdisplay_disp", .clk = NULL, }, { .name = "nvdisplay_p0", .clk = NULL, }, { .name = "nvdisp_dsc", .clk = NULL, }, { .name = "nvdisplay_p1", .clk = NULL, }, { .name = "nvdisplay_p2", .clk = NULL, }, }; static struct tegra_dc_pd_clk_info t18x_disp_pd1_clk_info[] = { { .name = "nvdisplay_p1", .clk = NULL, }, }; static struct tegra_dc_pd_clk_info t18x_disp_pd2_clk_info[] = { { .name = "nvdisplay_p2", .clk = NULL, }, }; /* * NOTE: Keep the following power domains ordered according to their head owner. */ static struct tegra_dc_pd_info t18x_disp_pd_info[] = { /* Head0 power domain */ { .of_id = { { .compatible = "nvidia,tegra186-disa-pd", }, {}, }, .pg_id = -1, .head_owner = 0, .head_mask = 0x1, /* Head(s): 0 */ .win_mask = 0x1, /* Window(s): 0 */ .domain_clks = t18x_disp_pd0_clk_info, .nclks = ARRAY_SIZE(t18x_disp_pd0_clk_info), .ref_cnt = 0, }, /* Head1 power domain */ { .of_id = { { .compatible = "nvidia,tegra186-disb-pd", }, {}, }, .pg_id = -1, .head_owner = 1, .head_mask = 0x2, /* Head(s): 1 */ .win_mask = 0x6, /* Window(s): 1,2 */ .domain_clks = t18x_disp_pd1_clk_info, .nclks = ARRAY_SIZE(t18x_disp_pd1_clk_info), .ref_cnt = 0, }, /* Head2 power domain */ { .of_id = { { .compatible = "nvidia,tegra186-disc-pd", }, {}, }, .pg_id = -1, .head_owner = 2, .head_mask = 0x4, /* Head(s): 2 */ .win_mask = 0x38, /* Window(s): 3,4,5 */ .domain_clks = t18x_disp_pd2_clk_info, .nclks = ARRAY_SIZE(t18x_disp_pd2_clk_info), .ref_cnt = 0, }, }; static struct tegra_dc_pd_table t18x_disp_pd_table = { .pd_entries = t18x_disp_pd_info, .npd = ARRAY_SIZE(t18x_disp_pd_info), }; static bool compclk_already_on = false, hubclk_already_on = false; static int cur_clk_client_index; static unsigned int default_srgb_regamma_lut[] = { 0x6000, 0x60CE, 0x619D, 0x626C, 0x632D, 0x63D4, 0x6469, 0x64F0, 0x656B, 0x65DF, 0x664A, 0x66B0, 0x6711, 0x676D, 0x67C4, 0x6819, 0x686A, 0x68B8, 0x6904, 0x694D, 0x6994, 0x69D8, 0x6A1B, 0x6A5D, 0x6A9C, 0x6ADA, 0x6B17, 0x6B52, 0x6B8C, 0x6BC5, 0x6BFD, 0x6C33, 0x6C69, 0x6C9E, 0x6CD1, 0x6D04, 0x6D36, 0x6D67, 0x6D98, 0x6DC7, 0x6DF6, 0x6E25, 0x6E52, 0x6E7F, 0x6EAC, 0x6ED7, 0x6F03, 0x6F2D, 0x6F58, 0x6F81, 0x6FAA, 0x6FD3, 0x6FFB, 0x7023, 0x704B, 0x7071, 0x7098, 0x70BE, 0x70E4, 0x7109, 0x712E, 0x7153, 0x7177, 0x719B, 0x71BF, 0x71E2, 0x7205, 0x7227, 0x724A, 0x726C, 0x728E, 0x72AF, 0x72D0, 0x72F1, 0x7312, 0x7333, 0x7353, 0x7373, 0x7392, 0x73B2, 0x73D1, 0x73F0, 0x740F, 0x742D, 0x744C, 0x746A, 0x7488, 0x74A6, 0x74C3, 0x74E0, 0x74FE, 0x751B, 0x7537, 0x7554, 0x7570, 0x758D, 0x75A9, 0x75C4, 0x75E0, 0x75FC, 0x7617, 0x7632, 0x764D, 0x7668, 0x7683, 0x769E, 0x76B8, 0x76D3, 0x76ED, 0x7707, 0x7721, 0x773B, 0x7754, 0x776E, 0x7787, 0x77A0, 0x77B9, 0x77D2, 0x77EB, 0x7804, 0x781D, 0x7835, 0x784E, 0x7866, 0x787E, 0x7896, 0x78AE, 0x78C6, 0x78DD, 0x78F5, 0x790D, 0x7924, 0x793B, 0x7952, 0x796A, 0x7981, 0x7997, 0x79AE, 0x79C5, 0x79DB, 0x79F2, 0x7A08, 0x7A1F, 0x7A35, 0x7A4B, 0x7A61, 0x7A77, 0x7A8D, 0x7AA3, 0x7AB8, 0x7ACE, 0x7AE3, 0x7AF9, 0x7B0E, 0x7B24, 0x7B39, 0x7B4E, 0x7B63, 0x7B78, 0x7B8D, 0x7BA2, 0x7BB6, 0x7BCB, 0x7BE0, 0x7BF4, 0x7C08, 0x7C1D, 0x7C31, 0x7C45, 0x7C59, 0x7C6E, 0x7C82, 0x7C96, 0x7CA9, 0x7CBD, 0x7CD1, 0x7CE5, 0x7CF8, 0x7D0C, 0x7D1F, 0x7D33, 0x7D46, 0x7D59, 0x7D6D, 0x7D80, 0x7D93, 0x7DA6, 0x7DB9, 0x7DCC, 0x7DDF, 0x7DF2, 0x7E04, 0x7E17, 0x7E2A, 0x7E3C, 0x7E4F, 0x7E61, 0x7E74, 0x7E86, 0x7E98, 0x7EAB, 0x7EBD, 0x7ECF, 0x7EE1, 0x7EF3, 0x7F05, 0x7F17, 0x7F29, 0x7F3B, 0x7F4D, 0x7F5E, 0x7F70, 0x7F82, 0x7F93, 0x7FA5, 0x7FB6, 0x7FC8, 0x7FD9, 0x7FEB, 0x7FFC, 0x800D, 0x801E, 0x8030, 0x8041, 0x8052, 0x8063, 0x8074, 0x8085, 0x8096, 0x80A7, 0x80B7, 0x80C8, 0x80D9, 0x80EA, 0x80FA, 0x810B, 0x811C, 0x812C, 0x813D, 0x814D, 0x815D, 0x816E, 0x817E, 0x818E, 0x819F, 0x81AF, 0x81BF, 0x81CF, 0x81DF, 0x81EF, 0x81FF, 0x820F, 0x821F, 0x822F, 0x823F, 0x824F, 0x825F, 0x826F, 0x827E, 0x828E, 0x829E, 0x82AD, 0x82BD, 0x82CC, 0x82DC, 0x82EB, 0x82FB, 0x830A, 0x831A, 0x8329, 0x8338, 0x8348, 0x8357, 0x8366, 0x8375, 0x8385, 0x8394, 0x83A3, 0x83B2, 0x83C1, 0x83D0, 0x83DF, 0x83EE, 0x83FD, 0x840C, 0x841A, 0x8429, 0x8438, 0x8447, 0x8455, 0x8464, 0x8473, 0x8481, 0x8490, 0x849F, 0x84AD, 0x84BC, 0x84CA, 0x84D9, 0x84E7, 0x84F5, 0x8504, 0x8512, 0x8521, 0x852F, 0x853D, 0x854B, 0x855A, 0x8568, 0x8576, 0x8584, 0x8592, 0x85A0, 0x85AE, 0x85BC, 0x85CA, 0x85D8, 0x85E6, 0x85F4, 0x8602, 0x8610, 0x861E, 0x862C, 0x8639, 0x8647, 0x8655, 0x8663, 0x8670, 0x867E, 0x868C, 0x8699, 0x86A7, 0x86B5, 0x86C2, 0x86D0, 0x86DD, 0x86EB, 0x86F8, 0x8705, 0x8713, 0x8720, 0x872E, 0x873B, 0x8748, 0x8756, 0x8763, 0x8770, 0x877D, 0x878B, 0x8798, 0x87A5, 0x87B2, 0x87BF, 0x87CC, 0x87D9, 0x87E6, 0x87F3, 0x8801, 0x880E, 0x881A, 0x8827, 0x8834, 0x8841, 0x884E, 0x885B, 0x8868, 0x8875, 0x8882, 0x888E, 0x889B, 0x88A8, 0x88B5, 0x88C1, 0x88CE, 0x88DB, 0x88E7, 0x88F4, 0x8900, 0x890D, 0x891A, 0x8926, 0x8933, 0x893F, 0x894C, 0x8958, 0x8965, 0x8971, 0x897D, 0x898A, 0x8996, 0x89A3, 0x89AF, 0x89BB, 0x89C8, 0x89D4, 0x89E0, 0x89EC, 0x89F9, 0x8A05, 0x8A11, 0x8A1D, 0x8A29, 0x8A36, 0x8A42, 0x8A4E, 0x8A5A, 0x8A66, 0x8A72, 0x8A7E, 0x8A8A, 0x8A96, 0x8AA2, 0x8AAE, 0x8ABA, 0x8AC6, 0x8AD2, 0x8ADE, 0x8AEA, 0x8AF5, 0x8B01, 0x8B0D, 0x8B19, 0x8B25, 0x8B31, 0x8B3C, 0x8B48, 0x8B54, 0x8B60, 0x8B6B, 0x8B77, 0x8B83, 0x8B8E, 0x8B9A, 0x8BA6, 0x8BB1, 0x8BBD, 0x8BC8, 0x8BD4, 0x8BDF, 0x8BEB, 0x8BF6, 0x8C02, 0x8C0D, 0x8C19, 0x8C24, 0x8C30, 0x8C3B, 0x8C47, 0x8C52, 0x8C5D, 0x8C69, 0x8C74, 0x8C80, 0x8C8B, 0x8C96, 0x8CA1, 0x8CAD, 0x8CB8, 0x8CC3, 0x8CCF, 0x8CDA, 0x8CE5, 0x8CF0, 0x8CFB, 0x8D06, 0x8D12, 0x8D1D, 0x8D28, 0x8D33, 0x8D3E, 0x8D49, 0x8D54, 0x8D5F, 0x8D6A, 0x8D75, 0x8D80, 0x8D8B, 0x8D96, 0x8DA1, 0x8DAC, 0x8DB7, 0x8DC2, 0x8DCD, 0x8DD8, 0x8DE3, 0x8DEE, 0x8DF9, 0x8E04, 0x8E0E, 0x8E19, 0x8E24, 0x8E2F, 0x8E3A, 0x8E44, 0x8E4F, 0x8E5A, 0x8E65, 0x8E6F, 0x8E7A, 0x8E85, 0x8E90, 0x8E9A, 0x8EA5, 0x8EB0, 0x8EBA, 0x8EC5, 0x8ECF, 0x8EDA, 0x8EE5, 0x8EEF, 0x8EFA, 0x8F04, 0x8F0F, 0x8F19, 0x8F24, 0x8F2E, 0x8F39, 0x8F43, 0x8F4E, 0x8F58, 0x8F63, 0x8F6D, 0x8F78, 0x8F82, 0x8F8C, 0x8F97, 0x8FA1, 0x8FAC, 0x8FB6, 0x8FC0, 0x8FCB, 0x8FD5, 0x8FDF, 0x8FEA, 0x8FF4, 0x8FFE, 0x9008, 0x9013, 0x901D, 0x9027, 0x9031, 0x903C, 0x9046, 0x9050, 0x905A, 0x9064, 0x906E, 0x9079, 0x9083, 0x908D, 0x9097, 0x90A1, 0x90AB, 0x90B5, 0x90BF, 0x90C9, 0x90D3, 0x90DD, 0x90E7, 0x90F1, 0x90FB, 0x9105, 0x910F, 0x9119, 0x9123, 0x912D, 0x9137, 0x9141, 0x914B, 0x9155, 0x915F, 0x9169, 0x9173, 0x917D, 0x9186, 0x9190, 0x919A, 0x91A4, 0x91AE, 0x91B8, 0x91C1, 0x91CB, 0x91D5, 0x91DF, 0x91E9, 0x91F2, 0x91FC, 0x9206, 0x9210, 0x9219, 0x9223, 0x922D, 0x9236, 0x9240, 0x924A, 0x9253, 0x925D, 0x9267, 0x9270, 0x927A, 0x9283, 0x928D, 0x9297, 0x92A0, 0x92AA, 0x92B3, 0x92BD, 0x92C6, 0x92D0, 0x92DA, 0x92E3, 0x92ED, 0x92F6, 0x9300, 0x9309, 0x9313, 0x931C, 0x9325, 0x932F, 0x9338, 0x9342, 0x934B, 0x9355, 0x935E, 0x9367, 0x9371, 0x937A, 0x9384, 0x938D, 0x9396, 0x93A0, 0x93A9, 0x93B2, 0x93BC, 0x93C5, 0x93CE, 0x93D7, 0x93E1, 0x93EA, 0x93F3, 0x93FC, 0x9406, 0x940F, 0x9418, 0x9421, 0x942B, 0x9434, 0x943D, 0x9446, 0x944F, 0x9459, 0x9462, 0x946B, 0x9474, 0x947D, 0x9486, 0x948F, 0x9499, 0x94A2, 0x94AB, 0x94B4, 0x94BD, 0x94C6, 0x94CF, 0x94D8, 0x94E1, 0x94EA, 0x94F3, 0x94FC, 0x9505, 0x950E, 0x9517, 0x9520, 0x9529, 0x9532, 0x953B, 0x9544, 0x954D, 0x9556, 0x955F, 0x9568, 0x9571, 0x957A, 0x9583, 0x958C, 0x9595, 0x959D, 0x95A6, 0x95AF, 0x95B8, 0x95C1, 0x95CA, 0x95D3, 0x95DB, 0x95E4, 0x95ED, 0x95F6, 0x95FF, 0x9608, 0x9610, 0x9619, 0x9622, 0x962B, 0x9633, 0x963C, 0x9645, 0x964E, 0x9656, 0x965F, 0x9668, 0x9671, 0x9679, 0x9682, 0x968B, 0x9693, 0x969C, 0x96A5, 0x96AD, 0x96B6, 0x96BF, 0x96C7, 0x96D0, 0x96D9, 0x96E1, 0x96EA, 0x96F2, 0x96FB, 0x9704, 0x970C, 0x9715, 0x971D, 0x9726, 0x972E, 0x9737, 0x9740, 0x9748, 0x9751, 0x9759, 0x9762, 0x976A, 0x9773, 0x977B, 0x9784, 0x978C, 0x9795, 0x979D, 0x97A6, 0x97AE, 0x97B6, 0x97BF, 0x97C7, 0x97D0, 0x97D8, 0x97E1, 0x97E9, 0x97F1, 0x97FA, 0x9802, 0x980B, 0x9813, 0x981B, 0x9824, 0x982C, 0x9834, 0x983D, 0x9845, 0x984D, 0x9856, 0x985E, 0x9866, 0x986F, 0x9877, 0x987F, 0x9888, 0x9890, 0x9898, 0x98A0, 0x98A9, 0x98B1, 0x98B9, 0x98C1, 0x98CA, 0x98D2, 0x98DA, 0x98E2, 0x98EB, 0x98F3, 0x98FB, 0x9903, 0x990B, 0x9914, 0x991C, 0x9924, 0x992C, 0x9934, 0x993C, 0x9945, 0x994D, 0x9955, 0x995D, 0x9965, 0x996D, 0x9975, 0x997D, 0x9986, 0x998E, 0x9996, 0x999E, 0x99A6, 0x99AE, 0x99B6, 0x99BE, 0x99C6, 0x99CE, 0x99D6, 0x99DE, 0x99E6, 0x99EE, 0x99F6, 0x99FE, 0x9A06, 0x9A0E, 0x9A16, 0x9A1E, 0x9A26, 0x9A2E, 0x9A36, 0x9A3E, 0x9A46, 0x9A4E, 0x9A56, 0x9A5E, 0x9A66, 0x9A6E, 0x9A76, 0x9A7E, 0x9A86, 0x9A8E, 0x9A96, 0x9A9D, 0x9AA5, 0x9AAD, 0x9AB5, 0x9ABD, 0x9AC5, 0x9ACD, 0x9AD5, 0x9ADC, 0x9AE4, 0x9AEC, 0x9AF4, 0x9AFC, 0x9B04, 0x9B0C, 0x9B13, 0x9B1B, 0x9B23, 0x9B2B, 0x9B33, 0x9B3A, 0x9B42, 0x9B4A, 0x9B52, 0x9B59, 0x9B61, 0x9B69, 0x9B71, 0x9B79, 0x9B80, 0x9B88, 0x9B90, 0x9B97, 0x9B9F, 0x9BA7, 0x9BAF, 0x9BB6, 0x9BBE, 0x9BC6, 0x9BCD, 0x9BD5, 0x9BDD, 0x9BE5, 0x9BEC, 0x9BF4, 0x9BFC, 0x9C03, 0x9C0B, 0x9C12, 0x9C1A, 0x9C22, 0x9C29, 0x9C31, 0x9C39, 0x9C40, 0x9C48, 0x9C50, 0x9C57, 0x9C5F, 0x9C66, 0x9C6E, 0x9C75, 0x9C7D, 0x9C85, 0x9C8C, 0x9C94, 0x9C9B, 0x9CA3, 0x9CAA, 0x9CB2, 0x9CBA, 0x9CC1, 0x9CC9, 0x9CD0, 0x9CD8, 0x9CDF, 0x9CE7, 0x9CEE, 0x9CF6, 0x9CFD, 0x9D05, 0x9D0C, 0x9D14, 0x9D1B, 0x9D23, 0x9D2A, 0x9D32, 0x9D39, 0x9D40, 0x9D48, 0x9D4F, 0x9D57, 0x9D5E, 0x9D66, 0x9D6D, 0x9D75, 0x9D7C, 0x9D83, 0x9D8B, 0x9D92, 0x9D9A, 0x9DA1, 0x9DA8, 0x9DB0, 0x9DB7, 0x9DBE, 0x9DC6, 0x9DCD, 0x9DD5, 0x9DDC, 0x9DE3, 0x9DEB, 0x9DF2, 0x9DF9, 0x9E01, 0x9E08, 0x9E0F, 0x9E17, 0x9E1E, 0x9E25, 0x9E2D, 0x9E34, 0x9E3B, 0x9E43, 0x9E4A, 0x9E51, 0x9E58, 0x9E60, 0x9E67, 0x9E6E, 0x9E75, 0x9E7D, 0x9E84, 0x9E8B, 0x9E92, 0x9E9A, 0x9EA1, 0x9EA8, 0x9EAF, 0x9EB7, 0x9EBE, 0x9EC5, 0x9ECC, 0x9ED4, 0x9EDB, 0x9EE2, 0x9EE9, 0x9EF0, 0x9EF7, 0x9EFF, 0x9F06, 0x9F0D, 0x9F14, 0x9F1B, 0x9F23, 0x9F2A, 0x9F31, 0x9F38, 0x9F3F, 0x9F46, 0x9F4D, 0x9F55, 0x9F5C, 0x9F63, 0x9F6A, 0x9F71, 0x9F78, 0x9F7F, 0x9F86, 0x9F8D, 0x9F95, 0x9F9C, 0x9FA3, 0x9FAA, 0x9FB1, 0x9FB8, 0x9FBF, 0x9FC6, 0x9FCD, 0x9FD4, 0x9FDB, 0x9FE2, 0x9FE9, 0x9FF0, 0x9FF7, 0x9FFF, }; static unsigned int yuv8_10bpc_regamma_lut[] = { 0x6000, 0x6047, 0x608F, 0x60D7, 0x611F, 0x6167, 0x61AF, 0x61F7, 0x623F, 0x6287, 0x62CF, 0x6317, 0x635F, 0x63A7, 0x63EF, 0x6437, 0x647F, 0x64C7, 0x650F, 0x655B, 0x65A1, 0x65E5, 0x6627, 0x6668, 0x66A7, 0x66E5, 0x6721, 0x675C, 0x6796, 0x67CF, 0x6806, 0x683D, 0x6873, 0x68A7, 0x68DB, 0x690E, 0x6941, 0x6972, 0x69A3, 0x69D3, 0x6A02, 0x6A31, 0x6A5F, 0x6A8D, 0x6ABA, 0x6AE6, 0x6B12, 0x6B3D, 0x6B68, 0x6B93, 0x6BBD, 0x6BE6, 0x6C0F, 0x6C37, 0x6C60, 0x6C87, 0x6CAF, 0x6CD6, 0x6CFC, 0x6D22, 0x6D48, 0x6D6E, 0x6D93, 0x6DB8, 0x6DDC, 0x6E00, 0x6E24, 0x6E48, 0x6E6B, 0x6E8E, 0x6EB1, 0x6ED3, 0x6EF6, 0x6F18, 0x6F39, 0x6F5B, 0x6F7C, 0x6F9D, 0x6FBE, 0x6FDE, 0x6FFE, 0x701E, 0x703E, 0x705E, 0x707D, 0x709C, 0x70BB, 0x70DA, 0x70F9, 0x7117, 0x7135, 0x7154, 0x7171, 0x718F, 0x71AD, 0x71CA, 0x71E7, 0x7204, 0x7221, 0x723E, 0x725A, 0x7277, 0x7293, 0x72AF, 0x72CB, 0x72E6, 0x7302, 0x731E, 0x7339, 0x7354, 0x736F, 0x738A, 0x73A5, 0x73C0, 0x73DA, 0x73F4, 0x740F, 0x7429, 0x7443, 0x745D, 0x7477, 0x7490, 0x74AA, 0x74C3, 0x74DD, 0x74F6, 0x750F, 0x7528, 0x7541, 0x755A, 0x7572, 0x758B, 0x75A3, 0x75BC, 0x75D4, 0x75EC, 0x7604, 0x761C, 0x7634, 0x764C, 0x7663, 0x767B, 0x7693, 0x76AA, 0x76C1, 0x76D8, 0x76F0, 0x7707, 0x771E, 0x7735, 0x774B, 0x7762, 0x7779, 0x778F, 0x77A6, 0x77BC, 0x77D2, 0x77E9, 0x77FF, 0x7815, 0x782B, 0x7841, 0x7857, 0x786C, 0x7882, 0x7898, 0x78AD, 0x78C3, 0x78D8, 0x78EE, 0x7903, 0x7918, 0x792D, 0x7942, 0x7957, 0x796C, 0x7981, 0x7996, 0x79AB, 0x79BF, 0x79D4, 0x79E8, 0x79FD, 0x7A11, 0x7A26, 0x7A3A, 0x7A4E, 0x7A63, 0x7A77, 0x7A8B, 0x7A9F, 0x7AB3, 0x7AC7, 0x7ADA, 0x7AEE, 0x7B02, 0x7B16, 0x7B29, 0x7B3D, 0x7B50, 0x7B64, 0x7B77, 0x7B8A, 0x7B9E, 0x7BB1, 0x7BC4, 0x7BD7, 0x7BEA, 0x7BFD, 0x7C10, 0x7C23, 0x7C36, 0x7C49, 0x7C5C, 0x7C6F, 0x7C81, 0x7C94, 0x7CA7, 0x7CB9, 0x7CCC, 0x7CDE, 0x7CF1, 0x7D03, 0x7D15, 0x7D27, 0x7D3A, 0x7D4C, 0x7D5E, 0x7D70, 0x7D82, 0x7D94, 0x7DA6, 0x7DB8, 0x7DCA, 0x7DDC, 0x7DEE, 0x7DFF, 0x7E11, 0x7E23, 0x7E34, 0x7E46, 0x7E58, 0x7E69, 0x7E7B, 0x7E8C, 0x7E9D, 0x7EAF, 0x7EC0, 0x7ED1, 0x7EE3, 0x7EF4, 0x7F05, 0x7F16, 0x7F27, 0x7F38, 0x7F49, 0x7F5A, 0x7F6B, 0x7F7C, 0x7F8D, 0x7F9E, 0x7FAF, 0x7FBF, 0x7FD0, 0x7FE1, 0x7FF1, 0x8002, 0x8013, 0x8023, 0x8034, 0x8044, 0x8055, 0x8065, 0x8075, 0x8086, 0x8096, 0x80A6, 0x80B7, 0x80C7, 0x80D7, 0x80E7, 0x80F7, 0x8107, 0x8118, 0x8128, 0x8138, 0x8148, 0x8157, 0x8167, 0x8177, 0x8187, 0x8197, 0x81A7, 0x81B6, 0x81C6, 0x81D6, 0x81E6, 0x81F5, 0x8205, 0x8214, 0x8224, 0x8234, 0x8243, 0x8252, 0x8262, 0x8271, 0x8281, 0x8290, 0x829F, 0x82AF, 0x82BE, 0x82CD, 0x82DD, 0x82EC, 0x82FB, 0x830A, 0x8319, 0x8328, 0x8337, 0x8346, 0x8355, 0x8364, 0x8373, 0x8382, 0x8391, 0x83A0, 0x83AF, 0x83BE, 0x83CD, 0x83DB, 0x83EA, 0x83F9, 0x8408, 0x8416, 0x8425, 0x8434, 0x8442, 0x8451, 0x845F, 0x846E, 0x847C, 0x848B, 0x8499, 0x84A8, 0x84B6, 0x84C5, 0x84D3, 0x84E1, 0x84F0, 0x84FE, 0x850C, 0x851B, 0x8529, 0x8537, 0x8545, 0x8553, 0x8562, 0x8570, 0x857E, 0x858C, 0x859A, 0x85A8, 0x85B6, 0x85C4, 0x85D2, 0x85E0, 0x85EE, 0x85FC, 0x860A, 0x8618, 0x8626, 0x8633, 0x8641, 0x864F, 0x865D, 0x866B, 0x8678, 0x8686, 0x8694, 0x86A1, 0x86AF, 0x86BD, 0x86CA, 0x86D8, 0x86E5, 0x86F3, 0x8701, 0x870E, 0x871C, 0x8729, 0x8737, 0x8744, 0x8751, 0x875F, 0x876C, 0x877A, 0x8787, 0x8794, 0x87A2, 0x87AF, 0x87BC, 0x87C9, 0x87D7, 0x87E4, 0x87F1, 0x87FE, 0x880B, 0x8819, 0x8826, 0x8833, 0x8840, 0x884D, 0x885A, 0x8867, 0x8874, 0x8881, 0x888E, 0x889B, 0x88A8, 0x88B5, 0x88C2, 0x88CF, 0x88DC, 0x88E9, 0x88F6, 0x8902, 0x890F, 0x891C, 0x8929, 0x8936, 0x8942, 0x894F, 0x895C, 0x8969, 0x8975, 0x8982, 0x898F, 0x899B, 0x89A8, 0x89B4, 0x89C1, 0x89CE, 0x89DA, 0x89E7, 0x89F3, 0x8A00, 0x8A0C, 0x8A19, 0x8A25, 0x8A32, 0x8A3E, 0x8A4B, 0x8A57, 0x8A63, 0x8A70, 0x8A7C, 0x8A89, 0x8A95, 0x8AA1, 0x8AAE, 0x8ABA, 0x8AC6, 0x8AD2, 0x8ADF, 0x8AEB, 0x8AF7, 0x8B03, 0x8B0F, 0x8B1C, 0x8B28, 0x8B34, 0x8B40, 0x8B4C, 0x8B58, 0x8B64, 0x8B70, 0x8B7D, 0x8B89, 0x8B95, 0x8BA1, 0x8BAD, 0x8BB9, 0x8BC5, 0x8BD1, 0x8BDD, 0x8BE9, 0x8BF4, 0x8C00, 0x8C0C, 0x8C18, 0x8C24, 0x8C30, 0x8C3C, 0x8C48, 0x8C53, 0x8C5F, 0x8C6B, 0x8C77, 0x8C83, 0x8C8E, 0x8C9A, 0x8CA6, 0x8CB1, 0x8CBD, 0x8CC9, 0x8CD5, 0x8CE0, 0x8CEC, 0x8CF8, 0x8D03, 0x8D0F, 0x8D1A, 0x8D26, 0x8D32, 0x8D3D, 0x8D49, 0x8D54, 0x8D60, 0x8D6B, 0x8D77, 0x8D82, 0x8D8E, 0x8D99, 0x8DA5, 0x8DB0, 0x8DBC, 0x8DC7, 0x8DD2, 0x8DDE, 0x8DE9, 0x8DF4, 0x8E00, 0x8E0B, 0x8E17, 0x8E22, 0x8E2D, 0x8E38, 0x8E44, 0x8E4F, 0x8E5A, 0x8E66, 0x8E71, 0x8E7C, 0x8E87, 0x8E92, 0x8E9E, 0x8EA9, 0x8EB4, 0x8EBF, 0x8ECA, 0x8ED6, 0x8EE1, 0x8EEC, 0x8EF7, 0x8F02, 0x8F0D, 0x8F18, 0x8F23, 0x8F2E, 0x8F39, 0x8F44, 0x8F4F, 0x8F5A, 0x8F65, 0x8F70, 0x8F7B, 0x8F86, 0x8F91, 0x8F9C, 0x8FA7, 0x8FB2, 0x8FBD, 0x8FC8, 0x8FD3, 0x8FDE, 0x8FE9, 0x8FF3, 0x8FFE, 0x9009, 0x9014, 0x901F, 0x902A, 0x9034, 0x903F, 0x904A, 0x9055, 0x905F, 0x906A, 0x9075, 0x9080, 0x908A, 0x9095, 0x90A0, 0x90AA, 0x90B5, 0x90C0, 0x90CB, 0x90D5, 0x90E0, 0x90EA, 0x90F5, 0x9100, 0x910A, 0x9115, 0x911F, 0x912A, 0x9135, 0x913F, 0x914A, 0x9154, 0x915F, 0x9169, 0x9174, 0x917E, 0x9189, 0x9193, 0x919E, 0x91A8, 0x91B3, 0x91BD, 0x91C7, 0x91D2, 0x91DC, 0x91E7, 0x91F1, 0x91FB, 0x9206, 0x9210, 0x921B, 0x9225, 0x922F, 0x923A, 0x9244, 0x924E, 0x9259, 0x9263, 0x926D, 0x9277, 0x9282, 0x928C, 0x9296, 0x92A0, 0x92AB, 0x92B5, 0x92BF, 0x92C9, 0x92D4, 0x92DE, 0x92E8, 0x92F2, 0x92FC, 0x9306, 0x9311, 0x931B, 0x9325, 0x932F, 0x9339, 0x9343, 0x934D, 0x9357, 0x9361, 0x936B, 0x9376, 0x9380, 0x938A, 0x9394, 0x939E, 0x93A8, 0x93B2, 0x93BC, 0x93C6, 0x93D0, 0x93DA, 0x93E4, 0x93EE, 0x93F8, 0x9402, 0x940B, 0x9415, 0x941F, 0x9429, 0x9433, 0x943D, 0x9447, 0x9451, 0x945B, 0x9465, 0x946E, 0x9478, 0x9482, 0x948C, 0x9496, 0x94A0, 0x94A9, 0x94B3, 0x94BD, 0x94C7, 0x94D1, 0x94DA, 0x94E4, 0x94EE, 0x94F8, 0x9501, 0x950B, 0x9515, 0x951F, 0x9528, 0x9532, 0x953C, 0x9545, 0x954F, 0x9559, 0x9562, 0x956C, 0x9576, 0x957F, 0x9589, 0x9593, 0x959C, 0x95A6, 0x95AF, 0x95B9, 0x95C3, 0x95CC, 0x95D6, 0x95DF, 0x95E9, 0x95F2, 0x95FC, 0x9606, 0x960F, 0x9619, 0x9622, 0x962C, 0x9635, 0x963F, 0x9648, 0x9652, 0x965B, 0x9665, 0x966E, 0x9677, 0x9681, 0x968A, 0x9694, 0x969D, 0x96A7, 0x96B0, 0x96B9, 0x96C3, 0x96CC, 0x96D6, 0x96DF, 0x96E8, 0x96F2, 0x96FB, 0x9705, 0x970E, 0x9717, 0x9721, 0x972A, 0x9733, 0x973C, 0x9746, 0x974F, 0x9758, 0x9762, 0x976B, 0x9774, 0x977D, 0x9787, 0x9790, 0x9799, 0x97A2, 0x97AC, 0x97B5, 0x97BE, 0x97C7, 0x97D1, 0x97DA, 0x97E3, 0x97EC, 0x97F5, 0x97FE, 0x9808, 0x9811, 0x981A, 0x9823, 0x982C, 0x9835, 0x983F, 0x9848, 0x9851, 0x985A, 0x9863, 0x986C, 0x9875, 0x987E, 0x9887, 0x9890, 0x9899, 0x98A3, 0x98AC, 0x98B5, 0x98BE, 0x98C7, 0x98D0, 0x98D9, 0x98E2, 0x98EB, 0x98F4, 0x98FD, 0x9906, 0x990F, 0x9918, 0x9921, 0x992A, 0x9933, 0x993C, 0x9945, 0x994E, 0x9956, 0x995F, 0x9968, 0x9971, 0x997A, 0x9983, 0x998C, 0x9995, 0x999E, 0x99A7, 0x99AF, 0x99B8, 0x99C1, 0x99CA, 0x99D3, 0x99DC, 0x99E5, 0x99ED, 0x99F6, 0x99FF, 0x9A08, 0x9A11, 0x9A1A, 0x9A22, 0x9A2B, 0x9A34, 0x9A3D, 0x9A46, 0x9A4E, 0x9A57, 0x9A60, 0x9A69, 0x9A71, 0x9A7A, 0x9A83, 0x9A8C, 0x9A94, 0x9A9D, 0x9AA6, 0x9AAE, 0x9AB7, 0x9AC0, 0x9AC9, 0x9AD1, 0x9ADA, 0x9AE3, 0x9AEB, 0x9AF4, 0x9AFD, 0x9B05, 0x9B0E, 0x9B17, 0x9B1F, 0x9B28, 0x9B30, 0x9B39, 0x9B42, 0x9B4A, 0x9B53, 0x9B5C, 0x9B64, 0x9B6D, 0x9B75, 0x9B7E, 0x9B86, 0x9B8F, 0x9B98, 0x9BA0, 0x9BA9, 0x9BB1, 0x9BBA, 0x9BC2, 0x9BCB, 0x9BD3, 0x9BDC, 0x9BE4, 0x9BED, 0x9BF5, 0x9BFE, 0x9C06, 0x9C0F, 0x9C17, 0x9C20, 0x9C28, 0x9C31, 0x9C39, 0x9C42, 0x9C4A, 0x9C53, 0x9C5B, 0x9C64, 0x9C6C, 0x9C74, 0x9C7D, 0x9C85, 0x9C8E, 0x9C96, 0x9C9F, 0x9CA7, 0x9CAF, 0x9CB8, 0x9CC0, 0x9CC9, 0x9CD1, 0x9CD9, 0x9CE2, 0x9CEA, 0x9CF2, 0x9CFB, 0x9D03, 0x9D0B, 0x9D14, 0x9D1C, 0x9D24, 0x9D2D, 0x9D35, 0x9D3D, 0x9D46, 0x9D4E, 0x9D56, 0x9D5E, 0x9D67, 0x9D6F, 0x9D77, 0x9D80, 0x9D88, 0x9D90, 0x9D98, 0x9DA1, 0x9DA9, 0x9DB1, 0x9DB9, 0x9DC2, 0x9DCA, 0x9DD2, 0x9DDA, 0x9DE2, 0x9DEB, 0x9DF3, 0x9DFB, 0x9E03, 0x9E0C, 0x9E14, 0x9E1C, 0x9E24, 0x9E2C, 0x9E34, 0x9E3D, 0x9E45, 0x9E4D, 0x9E55, 0x9E5D, 0x9E65, 0x9E6D, 0x9E76, 0x9E7E, 0x9E86, 0x9E8E, 0x9E96, 0x9E9E, 0x9EA6, 0x9EAE, 0x9EB6, 0x9EBF, 0x9EC7, 0x9ECF, 0x9ED7, 0x9EDF, 0x9EE7, 0x9EEF, 0x9EF7, 0x9EFF, 0x9F07, 0x9F0F, 0x9F17, 0x9F1F, 0x9F27, 0x9F2F, 0x9F37, 0x9F3F, 0x9F47, 0x9F4F, 0x9F57, 0x9F5F, 0x9F67, 0x9F6F, 0x9F77, 0x9F7F, 0x9F87, 0x9F8F, 0x9F97, 0x9F9F, 0x9FA7, 0x9FAF, 0x9FB7, 0x9FBF, 0x9FC7, 0x9FCF, 0x9FD7, 0x9FDF, 0x9FE7, 0x9FEF, 0x9FF7, 0x9FFF, }; static unsigned int yuv12bpc_regamma_lut[] = { 0x6000, 0x6047, 0x608F, 0x60D7, 0x611F, 0x6167, 0x61AF, 0x61F7, 0x623F, 0x6287, 0x62CF, 0x6317, 0x635F, 0x63A7, 0x63EF, 0x6437, 0x647F, 0x64C7, 0x650F, 0x6557, 0x659D, 0x65E1, 0x6623, 0x6664, 0x66A3, 0x66E1, 0x671D, 0x6758, 0x6792, 0x67CB, 0x6802, 0x6839, 0x686F, 0x68A4, 0x68D7, 0x690B, 0x693D, 0x696E, 0x699F, 0x69CF, 0x69FF, 0x6A2D, 0x6A5C, 0x6A89, 0x6AB6, 0x6AE3, 0x6B0E, 0x6B3A, 0x6B65, 0x6B8F, 0x6BB9, 0x6BE2, 0x6C0B, 0x6C34, 0x6C5C, 0x6C84, 0x6CAB, 0x6CD2, 0x6CF9, 0x6D1F, 0x6D45, 0x6D6A, 0x6D8F, 0x6DB4, 0x6DD9, 0x6DFD, 0x6E21, 0x6E44, 0x6E68, 0x6E8B, 0x6EAD, 0x6ED0, 0x6EF2, 0x6F14, 0x6F36, 0x6F57, 0x6F78, 0x6F99, 0x6FBA, 0x6FDB, 0x6FFB, 0x701B, 0x703B, 0x705A, 0x707A, 0x7099, 0x70B8, 0x70D7, 0x70F6, 0x7114, 0x7132, 0x7150, 0x716E, 0x718C, 0x71A9, 0x71C7, 0x71E4, 0x7201, 0x721E, 0x723A, 0x7257, 0x7273, 0x7290, 0x72AC, 0x72C8, 0x72E3, 0x72FF, 0x731A, 0x7336, 0x7351, 0x736C, 0x7387, 0x73A2, 0x73BC, 0x73D7, 0x73F1, 0x740C, 0x7426, 0x7440, 0x745A, 0x7474, 0x748D, 0x74A7, 0x74C0, 0x74DA, 0x74F3, 0x750C, 0x7525, 0x753E, 0x7557, 0x756F, 0x7588, 0x75A0, 0x75B9, 0x75D1, 0x75E9, 0x7601, 0x7619, 0x7631, 0x7649, 0x7661, 0x7678, 0x7690, 0x76A7, 0x76BE, 0x76D6, 0x76ED, 0x7704, 0x771B, 0x7732, 0x7748, 0x775F, 0x7776, 0x778C, 0x77A3, 0x77B9, 0x77D0, 0x77E6, 0x77FC, 0x7812, 0x7828, 0x783E, 0x7854, 0x786A, 0x787F, 0x7895, 0x78AB, 0x78C0, 0x78D5, 0x78EB, 0x7900, 0x7915, 0x792A, 0x7940, 0x7955, 0x796A, 0x797E, 0x7993, 0x79A8, 0x79BD, 0x79D1, 0x79E6, 0x79FA, 0x7A0F, 0x7A23, 0x7A37, 0x7A4C, 0x7A60, 0x7A74, 0x7A88, 0x7A9C, 0x7AB0, 0x7AC4, 0x7AD8, 0x7AEC, 0x7AFF, 0x7B13, 0x7B27, 0x7B3A, 0x7B4E, 0x7B61, 0x7B75, 0x7B88, 0x7B9B, 0x7BAE, 0x7BC2, 0x7BD5, 0x7BE8, 0x7BFB, 0x7C0E, 0x7C21, 0x7C34, 0x7C47, 0x7C59, 0x7C6C, 0x7C7F, 0x7C92, 0x7CA4, 0x7CB7, 0x7CC9, 0x7CDC, 0x7CEE, 0x7D00, 0x7D13, 0x7D25, 0x7D37, 0x7D49, 0x7D5C, 0x7D6E, 0x7D80, 0x7D92, 0x7DA4, 0x7DB6, 0x7DC8, 0x7DD9, 0x7DEB, 0x7DFD, 0x7E0F, 0x7E20, 0x7E32, 0x7E44, 0x7E55, 0x7E67, 0x7E78, 0x7E8A, 0x7E9B, 0x7EAC, 0x7EBE, 0x7ECF, 0x7EE0, 0x7EF1, 0x7F03, 0x7F14, 0x7F25, 0x7F36, 0x7F47, 0x7F58, 0x7F69, 0x7F7A, 0x7F8B, 0x7F9B, 0x7FAC, 0x7FBD, 0x7FCE, 0x7FDE, 0x7FEF, 0x8000, 0x8010, 0x8021, 0x8031, 0x8042, 0x8052, 0x8063, 0x8073, 0x8084, 0x8094, 0x80A4, 0x80B4, 0x80C5, 0x80D5, 0x80E5, 0x80F5, 0x8105, 0x8115, 0x8125, 0x8135, 0x8145, 0x8155, 0x8165, 0x8175, 0x8185, 0x8195, 0x81A5, 0x81B4, 0x81C4, 0x81D4, 0x81E3, 0x81F3, 0x8203, 0x8212, 0x8222, 0x8231, 0x8241, 0x8250, 0x8260, 0x826F, 0x827F, 0x828E, 0x829D, 0x82AD, 0x82BC, 0x82CB, 0x82DA, 0x82EA, 0x82F9, 0x8308, 0x8317, 0x8326, 0x8335, 0x8344, 0x8353, 0x8362, 0x8371, 0x8380, 0x838F, 0x839E, 0x83AD, 0x83BC, 0x83CB, 0x83D9, 0x83E8, 0x83F7, 0x8406, 0x8414, 0x8423, 0x8432, 0x8440, 0x844F, 0x845D, 0x846C, 0x847A, 0x8489, 0x8497, 0x84A6, 0x84B4, 0x84C3, 0x84D1, 0x84DF, 0x84EE, 0x84FC, 0x850A, 0x8519, 0x8527, 0x8535, 0x8543, 0x8552, 0x8560, 0x856E, 0x857C, 0x858A, 0x8598, 0x85A6, 0x85B4, 0x85C2, 0x85D0, 0x85DE, 0x85EC, 0x85FA, 0x8608, 0x8616, 0x8624, 0x8632, 0x863F, 0x864D, 0x865B, 0x8669, 0x8677, 0x8684, 0x8692, 0x86A0, 0x86AD, 0x86BB, 0x86C9, 0x86D6, 0x86E4, 0x86F1, 0x86FF, 0x870C, 0x871A, 0x8727, 0x8735, 0x8742, 0x8750, 0x875D, 0x876B, 0x8778, 0x8785, 0x8793, 0x87A0, 0x87AD, 0x87BA, 0x87C8, 0x87D5, 0x87E2, 0x87EF, 0x87FD, 0x880A, 0x8817, 0x8824, 0x8831, 0x883E, 0x884B, 0x8858, 0x8866, 0x8873, 0x8880, 0x888D, 0x889A, 0x88A7, 0x88B3, 0x88C0, 0x88CD, 0x88DA, 0x88E7, 0x88F4, 0x8901, 0x890E, 0x891A, 0x8927, 0x8934, 0x8941, 0x894E, 0x895A, 0x8967, 0x8974, 0x8980, 0x898D, 0x899A, 0x89A6, 0x89B3, 0x89BF, 0x89CC, 0x89D9, 0x89E5, 0x89F2, 0x89FE, 0x8A0B, 0x8A17, 0x8A24, 0x8A30, 0x8A3D, 0x8A49, 0x8A56, 0x8A62, 0x8A6E, 0x8A7B, 0x8A87, 0x8A93, 0x8AA0, 0x8AAC, 0x8AB8, 0x8AC5, 0x8AD1, 0x8ADD, 0x8AE9, 0x8AF6, 0x8B02, 0x8B0E, 0x8B1A, 0x8B26, 0x8B32, 0x8B3F, 0x8B4B, 0x8B57, 0x8B63, 0x8B6F, 0x8B7B, 0x8B87, 0x8B93, 0x8B9F, 0x8BAB, 0x8BB7, 0x8BC3, 0x8BCF, 0x8BDB, 0x8BE7, 0x8BF3, 0x8BFF, 0x8C0B, 0x8C17, 0x8C23, 0x8C2E, 0x8C3A, 0x8C46, 0x8C52, 0x8C5E, 0x8C6A, 0x8C75, 0x8C81, 0x8C8D, 0x8C99, 0x8CA4, 0x8CB0, 0x8CBC, 0x8CC8, 0x8CD3, 0x8CDF, 0x8CEB, 0x8CF6, 0x8D02, 0x8D0D, 0x8D19, 0x8D25, 0x8D30, 0x8D3C, 0x8D47, 0x8D53, 0x8D5E, 0x8D6A, 0x8D75, 0x8D81, 0x8D8C, 0x8D98, 0x8DA3, 0x8DAF, 0x8DBA, 0x8DC6, 0x8DD1, 0x8DDC, 0x8DE8, 0x8DF3, 0x8DFF, 0x8E0A, 0x8E15, 0x8E21, 0x8E2C, 0x8E37, 0x8E43, 0x8E4E, 0x8E59, 0x8E64, 0x8E70, 0x8E7B, 0x8E86, 0x8E91, 0x8E9C, 0x8EA8, 0x8EB3, 0x8EBE, 0x8EC9, 0x8ED4, 0x8EDF, 0x8EEB, 0x8EF6, 0x8F01, 0x8F0C, 0x8F17, 0x8F22, 0x8F2D, 0x8F38, 0x8F43, 0x8F4E, 0x8F59, 0x8F64, 0x8F6F, 0x8F7A, 0x8F85, 0x8F90, 0x8F9B, 0x8FA6, 0x8FB1, 0x8FBC, 0x8FC7, 0x8FD2, 0x8FDD, 0x8FE7, 0x8FF2, 0x8FFD, 0x9008, 0x9013, 0x901E, 0x9028, 0x9033, 0x903E, 0x9049, 0x9054, 0x905E, 0x9069, 0x9074, 0x907F, 0x9089, 0x9094, 0x909F, 0x90A9, 0x90B4, 0x90BF, 0x90C9, 0x90D4, 0x90DF, 0x90E9, 0x90F4, 0x90FF, 0x9109, 0x9114, 0x911E, 0x9129, 0x9134, 0x913E, 0x9149, 0x9153, 0x915E, 0x9168, 0x9173, 0x917D, 0x9188, 0x9192, 0x919D, 0x91A7, 0x91B2, 0x91BC, 0x91C6, 0x91D1, 0x91DB, 0x91E6, 0x91F0, 0x91FB, 0x9205, 0x920F, 0x921A, 0x9224, 0x922E, 0x9239, 0x9243, 0x924D, 0x9258, 0x9262, 0x926C, 0x9276, 0x9281, 0x928B, 0x9295, 0x929F, 0x92AA, 0x92B4, 0x92BE, 0x92C8, 0x92D3, 0x92DD, 0x92E7, 0x92F1, 0x92FB, 0x9305, 0x9310, 0x931A, 0x9324, 0x932E, 0x9338, 0x9342, 0x934C, 0x9356, 0x9360, 0x936B, 0x9375, 0x937F, 0x9389, 0x9393, 0x939D, 0x93A7, 0x93B1, 0x93BB, 0x93C5, 0x93CF, 0x93D9, 0x93E3, 0x93ED, 0x93F7, 0x9401, 0x940B, 0x9415, 0x941E, 0x9428, 0x9432, 0x943C, 0x9446, 0x9450, 0x945A, 0x9464, 0x946E, 0x9477, 0x9481, 0x948B, 0x9495, 0x949F, 0x94A9, 0x94B2, 0x94BC, 0x94C6, 0x94D0, 0x94DA, 0x94E3, 0x94ED, 0x94F7, 0x9501, 0x950A, 0x9514, 0x951E, 0x9527, 0x9531, 0x953B, 0x9545, 0x954E, 0x9558, 0x9562, 0x956B, 0x9575, 0x957F, 0x9588, 0x9592, 0x959B, 0x95A5, 0x95AF, 0x95B8, 0x95C2, 0x95CB, 0x95D5, 0x95DF, 0x95E8, 0x95F2, 0x95FB, 0x9605, 0x960E, 0x9618, 0x9621, 0x962B, 0x9634, 0x963E, 0x9647, 0x9651, 0x965A, 0x9664, 0x966D, 0x9677, 0x9680, 0x968A, 0x9693, 0x969D, 0x96A6, 0x96AF, 0x96B9, 0x96C2, 0x96CC, 0x96D5, 0x96DE, 0x96E8, 0x96F1, 0x96FB, 0x9704, 0x970D, 0x9717, 0x9720, 0x9729, 0x9733, 0x973C, 0x9745, 0x974E, 0x9758, 0x9761, 0x976A, 0x9774, 0x977D, 0x9786, 0x978F, 0x9799, 0x97A2, 0x97AB, 0x97B4, 0x97BE, 0x97C7, 0x97D0, 0x97D9, 0x97E2, 0x97EC, 0x97F5, 0x97FE, 0x9807, 0x9810, 0x9819, 0x9823, 0x982C, 0x9835, 0x983E, 0x9847, 0x9850, 0x9859, 0x9862, 0x986C, 0x9875, 0x987E, 0x9887, 0x9890, 0x9899, 0x98A2, 0x98AB, 0x98B4, 0x98BD, 0x98C6, 0x98CF, 0x98D8, 0x98E1, 0x98EA, 0x98F3, 0x98FC, 0x9905, 0x990E, 0x9917, 0x9920, 0x9929, 0x9932, 0x993B, 0x9944, 0x994D, 0x9956, 0x995F, 0x9968, 0x9971, 0x997A, 0x9983, 0x998B, 0x9994, 0x999D, 0x99A6, 0x99AF, 0x99B8, 0x99C1, 0x99CA, 0x99D3, 0x99DB, 0x99E4, 0x99ED, 0x99F6, 0x99FF, 0x9A08, 0x9A10, 0x9A19, 0x9A22, 0x9A2B, 0x9A34, 0x9A3C, 0x9A45, 0x9A4E, 0x9A57, 0x9A5F, 0x9A68, 0x9A71, 0x9A7A, 0x9A82, 0x9A8B, 0x9A94, 0x9A9D, 0x9AA5, 0x9AAE, 0x9AB7, 0x9ABF, 0x9AC8, 0x9AD1, 0x9ADA, 0x9AE2, 0x9AEB, 0x9AF4, 0x9AFC, 0x9B05, 0x9B0E, 0x9B16, 0x9B1F, 0x9B28, 0x9B30, 0x9B39, 0x9B41, 0x9B4A, 0x9B53, 0x9B5B, 0x9B64, 0x9B6C, 0x9B75, 0x9B7E, 0x9B86, 0x9B8F, 0x9B97, 0x9BA0, 0x9BA8, 0x9BB1, 0x9BBA, 0x9BC2, 0x9BCB, 0x9BD3, 0x9BDC, 0x9BE4, 0x9BED, 0x9BF5, 0x9BFE, 0x9C06, 0x9C0F, 0x9C17, 0x9C20, 0x9C28, 0x9C31, 0x9C39, 0x9C42, 0x9C4A, 0x9C52, 0x9C5B, 0x9C63, 0x9C6C, 0x9C74, 0x9C7D, 0x9C85, 0x9C8D, 0x9C96, 0x9C9E, 0x9CA7, 0x9CAF, 0x9CB8, 0x9CC0, 0x9CC8, 0x9CD1, 0x9CD9, 0x9CE1, 0x9CEA, 0x9CF2, 0x9CFA, 0x9D03, 0x9D0B, 0x9D13, 0x9D1C, 0x9D24, 0x9D2C, 0x9D35, 0x9D3D, 0x9D45, 0x9D4E, 0x9D56, 0x9D5E, 0x9D67, 0x9D6F, 0x9D77, 0x9D7F, 0x9D88, 0x9D90, 0x9D98, 0x9DA0, 0x9DA9, 0x9DB1, 0x9DB9, 0x9DC1, 0x9DCA, 0x9DD2, 0x9DDA, 0x9DE2, 0x9DEB, 0x9DF3, 0x9DFB, 0x9E03, 0x9E0B, 0x9E14, 0x9E1C, 0x9E24, 0x9E2C, 0x9E34, 0x9E3C, 0x9E45, 0x9E4D, 0x9E55, 0x9E5D, 0x9E65, 0x9E6D, 0x9E75, 0x9E7E, 0x9E86, 0x9E8E, 0x9E96, 0x9E9E, 0x9EA6, 0x9EAE, 0x9EB6, 0x9EBE, 0x9EC7, 0x9ECF, 0x9ED7, 0x9EDF, 0x9EE7, 0x9EEF, 0x9EF7, 0x9EFF, 0x9F07, 0x9F0F, 0x9F17, 0x9F1F, 0x9F27, 0x9F2F, 0x9F37, 0x9F3F, 0x9F47, 0x9F4F, 0x9F57, 0x9F5F, 0x9F67, 0x9F6F, 0x9F77, 0x9F7F, 0x9F87, 0x9F8F, 0x9F97, 0x9F9F, 0x9FA7, 0x9FAF, 0x9FB7, 0x9FBF, 0x9FC7, 0x9FCF, 0x9FD7, 0x9FDF, 0x9FE7, 0x9FEF, 0x9FF7, 0x9FFF, }; void tegra_nvdisp_set_background_color(struct tegra_dc *dc, u32 background_color) { tegra_dc_writel(dc, background_color, nvdisp_background_color_r()); } static void tegra_nvdisp_mode_set_background_color(struct tegra_dc *dc) { u32 reg_val = nvdisp_background_color_reset_val_v(); if (dc->yuv_bypass) { int yuv_flag = dc->mode.vmode & FB_VMODE_SET_YUV_MASK; switch (yuv_flag) { case FB_VMODE_Y420 | FB_VMODE_Y24: reg_val = RGB_TO_YUV420_8BPC_BLACK_PIX; break; case FB_VMODE_Y420 | FB_VMODE_Y30: reg_val = RGB_TO_YUV420_10BPC_BLACK_PIX; break; case FB_VMODE_Y422 | FB_VMODE_Y36: reg_val = RGB_TO_YUV422_10BPC_BLACK_PIX; break; case FB_VMODE_Y444 | FB_VMODE_Y24: reg_val = RGB_TO_YUV444_8BPC_BLACK_PIX; break; default: dev_err(&dc->ndev->dev, "%s: unverified bypass mode = 0x%x\n", __func__, dc->mode.vmode); break; } } tegra_nvdisp_set_background_color(dc, reg_val); } static inline void tegra_nvdisp_program_curs_dvfs_watermark(struct tegra_dc *dc, u32 dvfs_watermark); static inline void tegra_nvdisp_program_win_dvfs_watermark( struct tegra_dc_win *win, u32 dvfs_watermark); static inline void tegra_nvdisp_program_common_fetch_meter(struct tegra_dc *dc, u32 curs_slots, u32 win_slots); static void tegra_nvdisp_program_imp_curs_entries(struct tegra_dc *dc, u32 fetch_slots, u32 pipe_meter, u32 dvfs_watermark, u32 mempool_entries, int owner_head); static void tegra_nvdisp_program_imp_win_entries(struct tegra_dc *dc, u32 thread_group, u32 fetch_slots, u32 pipe_meter, u32 dvfs_watermark, u32 mempool_entries, u32 win_id, int owner_head); static inline bool tegra_nvdisp_is_dc_active(struct tegra_dc *dc) { u32 ctrl_mode = 0; if (!dc || !dc->enabled) return false; ctrl_mode = tegra_dc_readl(dc, nvdisp_display_command_r()); return (ctrl_mode != nvdisp_display_command_control_mode_stop_f()); } static int tegra_nvdisp_program_output_lut(struct tegra_dc *dc, struct tegra_dc_nvdisp_lut *nvdisp_lut) { tegra_dc_writel(dc, tegra_dc_reg_l32(nvdisp_lut->phy_addr), nvdisp_output_lut_base_r()); tegra_dc_writel(dc, tegra_dc_reg_h32(nvdisp_lut->phy_addr), nvdisp_output_lut_base_hi_r()); tegra_dc_writel(dc, nvdisp_output_lut_ctl_size_1025_f() | nvdisp_output_lut_ctl_mode_f(0x1), /* interpolate */ nvdisp_output_lut_ctl_r()); return 0; } static void nvdisp_copy_output_lut(u64 *dst, unsigned int *src, int size) { int i; u64 r = 0; for (i = 0; i < size; i++) { r = src[i]; dst[i] = (r << 32) | (r << 16) | r; } } void tegra_nvdisp_get_default_cmu(struct tegra_dc_nvdisp_cmu *default_cmu) { nvdisp_copy_output_lut(default_cmu->rgb, default_srgb_regamma_lut, NVDISP_OUTPUT_LUT_SIZE); } static int nvdisp_alloc_output_lut(struct tegra_dc *dc) { struct tegra_dc_nvdisp_lut *nvdisp_lut = &dc->nvdisp_postcomp_lut; if (!nvdisp_lut) return -ENOMEM; /* Allocate the memory for LUT */ nvdisp_lut->size = NVDISP_OUTPUT_LUT_SIZE * sizeof(u64); nvdisp_lut->rgb = (u64 *)dma_zalloc_coherent(&dc->ndev->dev, nvdisp_lut->size, &nvdisp_lut->phy_addr, GFP_KERNEL); if (!nvdisp_lut->rgb) return -ENOMEM; /* Init LUT with cmu data provided from DT file */ if (dc->pdata->nvdisp_cmu && dc->pdata->cmu_enable) { memcpy(nvdisp_lut->rgb, dc->pdata->nvdisp_cmu->rgb, nvdisp_lut->size); return 0; } /* Init the LUT table with default sRGB values */ nvdisp_copy_output_lut(nvdisp_lut->rgb, default_srgb_regamma_lut, NVDISP_OUTPUT_LUT_SIZE); return 0; } static int nvdisp_alloc_input_lut(struct tegra_dc *dc, struct tegra_dc_win *win, bool winlut) { struct tegra_dc_nvdisp_lut *nvdisp_lut; if (winlut) nvdisp_lut = &win->nvdisp_lut; else nvdisp_lut = &dc->fb_nvdisp_lut; if (!nvdisp_lut) return -ENOMEM; /* Allocate the memory for LUT */ nvdisp_lut->size = NVDISP_INPUT_LUT_SIZE * sizeof(u64); nvdisp_lut->rgb = (u64 *)dma_zalloc_coherent(&dc->ndev->dev, nvdisp_lut->size, &nvdisp_lut->phy_addr, GFP_KERNEL); if (!nvdisp_lut->rgb) return -ENOMEM; return 0; } /* Deassert all the common nvdisplay resets. * Misc and all windows groups are placed in common. */ static int __maybe_unused tegra_nvdisp_common_reset_deassert(struct tegra_dc *dc) { u8 i; int err; for (i = 0; i < tegra_dc_get_numof_dispwindows(); i++) { if (!nvdisp_common_rst[i]) { dev_err(&dc->ndev->dev, "No nvdisp resets available\n"); return -EINVAL; } err = reset_control_deassert(nvdisp_common_rst[i]); if (err) { dev_err(&dc->ndev->dev, "Unable to reset misc\n"); return err; } } return 0; } static int __maybe_unused tegra_nvdisp_common_reset_assert(struct tegra_dc *dc) { u8 i; int err; for (i = 0; i < tegra_dc_get_numof_dispwindows(); i++) { if (!nvdisp_common_rst[i]) { dev_err(&dc->ndev->dev, "No Nvdisp resets available\n"); return -EINVAL; } err = reset_control_assert(nvdisp_common_rst[i]); if (err) { dev_err(&dc->ndev->dev, "Unable to reset misc\n"); return err; } } return 0; } static int __maybe_unused tegra_nvdisp_wgrp_reset_assert(struct tegra_dc *dc) { int idx, err = 0; for_each_set_bit(idx, &dc->valid_windows, tegra_dc_get_numof_dispwindows()) { err = reset_control_assert(nvdisp_common_rst[idx+1]); if (err) dev_err(&dc->ndev->dev, "Failed window %d rst\n", idx); } return err; } static int tegra_nvdisp_wgrp_reset_deassert(struct tegra_dc *dc) { int idx, err = 0; /* Misc deassert is common for all windows */ err = reset_control_deassert(nvdisp_common_rst[0]); if (err) dev_err(&dc->ndev->dev, "Failed Misc deassert\n"); for_each_set_bit(idx, &dc->valid_windows, tegra_dc_get_numof_dispwindows()) { err = reset_control_deassert(nvdisp_common_rst[idx+1]); if (err) dev_err(&dc->ndev->dev, "Failed window deassert\n"); } return err; } static int tegra_nvdisp_reset_prepare(struct tegra_dc *dc) { char rst_name[6]; int i; /* Continue if bpmp is enabled or sim */ if (!tegra_bpmp_running()) { if (tegra_platform_is_vdk()) dev_err(&dc->ndev->dev, "Continue without BPMP for sim\n"); else return 0; } nvdisp_common_rst[0] = devm_reset_control_get(&dc->ndev->dev, "misc"); if (IS_ERR(nvdisp_common_rst[0])) { dev_err(&dc->ndev->dev, "Unable to get misc reset\n"); return PTR_ERR(nvdisp_common_rst[0]); } for (i = 0; i < tegra_dc_get_numof_dispwindows(); i++) { snprintf(rst_name, sizeof(rst_name), "wgrp%u", i); nvdisp_common_rst[i+1] = devm_reset_control_get(&dc->ndev->dev, rst_name); if (IS_ERR(nvdisp_common_rst[i+1])) { dev_err(&dc->ndev->dev,"Unable to get %s reset\n", rst_name); return PTR_ERR(nvdisp_common_rst[i+1]); } } return 0; } int tegra_nvdisp_set_compclk(struct tegra_dc *dc) { int i, index = 0; unsigned long rate = 0; struct tegra_dc *dc_other = NULL; /* comp clk will be maximum of head0/1/2 */ mutex_lock(&tegra_nvdisp_lock); for (i = 0; i < tegra_dc_get_numof_dispheads(); i++) { dc_other = tegra_dc_get_dc(i); if (dc_other && dc_other->comp_clk_inuse && rate <= clk_get_rate(dc_other->clk)) { rate = clk_get_rate(dc_other->clk); index = i; } } if (rate == 0) { /* none of the pclks is on. disable compclk */ if (compclk_already_on) { tegra_disp_clk_disable_unprepare(compclk); compclk_already_on = false; } mutex_unlock(&tegra_nvdisp_lock); return 0; } dc_other = tegra_dc_get_dc(index); BUG_ON(!dc_other); pr_debug(" rate get on compclk %ld\n", rate); /* save current clock client index */ cur_clk_client_index = index; /* Set parent for Display clock */ clk_set_parent(compclk, dc_other->clk); /* Enable Display comp clock */ if (!compclk_already_on) { tegra_disp_clk_prepare_enable(compclk); compclk_already_on = true; } mutex_unlock(&tegra_nvdisp_lock); return 0; } int tegra_nvdisp_test_and_set_compclk(unsigned long rate, struct tegra_dc *dc) { bool inuse_state; mutex_lock(&tegra_nvdisp_lock); /* check if current dc->clk is the source of compclk and whether * rate to which dc->clk is going to be changed is less than current * compclk rate. If yes, it is necessary to switch compclk source to * next highest dc->clk. Otherwise current dc->clk acting as parent to * compclk will lower its rate causing compclk rate to get lowered * and head will underflow for brief period before compclk is again * set to highest of all dc->clks */ if (cur_clk_client_index == dc->ctrl_num && rate < clk_get_rate(compclk)) { inuse_state = dc->comp_clk_inuse; /* set inuse flag to false to make sure current * dc->clk will not be used as source for compclk */ dc->comp_clk_inuse = false; mutex_unlock(&tegra_nvdisp_lock); tegra_nvdisp_set_compclk(dc); dc->comp_clk_inuse = inuse_state; return 0; } mutex_unlock(&tegra_nvdisp_lock); return 0; } static int _tegra_nvdisp_init_pd_table(struct tegra_dc *dc) { struct tegra_dc_pd_table *pd_table = tegra_dc_get_disp_pd_table(); struct tegra_dc_pd_info *pds = pd_table->pd_entries; int npower_domains = pd_table->npd; int i; mutex_init(&pd_table->pd_lock); for (i = 0; i < npower_domains; i++) { struct tegra_dc_pd_info *pd = &pds[i]; struct tegra_dc_pd_clk_info *domain_clks = pd->domain_clks; int nclks = pd->nclks; int j; /* Fill in the powergate id for this power domain. */ pd->pg_id = tegra_pd_get_powergate_id(pd->of_id); /* Query all the required clocks for this power domain. */ for (j = 0; j < nclks; j++) { struct tegra_dc_pd_clk_info *domain_clk; domain_clk = &domain_clks[j]; domain_clk->clk = tegra_disp_clk_get(&dc->ndev->dev, domain_clk->name); if (IS_ERR_OR_NULL(domain_clk->clk)) { dev_err(&dc->ndev->dev, "can't get %s clock\n", domain_clk->name); return -ENOENT; } } } return 0; } static void _tegra_nvdisp_destroy_pd_table(struct tegra_dc *dc) { struct tegra_dc_pd_table *pd_table = tegra_dc_get_disp_pd_table(); struct tegra_dc_pd_info *pds = pd_table->pd_entries; int npower_domains = pd_table->npd; int i; for (i = 0; i < npower_domains; i++) { struct tegra_dc_pd_info *pd = &pds[i]; struct tegra_dc_pd_clk_info *domain_clks = pd->domain_clks; int nclks = pd->nclks; int j; /* Release all the required clocks for this power domain. */ for (j = 0; j < nclks; j++) { struct tegra_dc_pd_clk_info *domain_clk; domain_clk = &domain_clks[j]; if (IS_ERR_OR_NULL(domain_clk->clk)) continue; tegra_disp_clk_put(&dc->ndev->dev, domain_clk->clk); } } } static void tegra_nvdisp_init_imp_wqs(void) { struct nvdisp_request_wq *reservation_wq; struct nvdisp_request_wq *promotion_wq; int num_heads = tegra_dc_get_numof_dispheads(); int timeout; reservation_wq = &g_imp.common_channel_reservation_wq; promotion_wq = &g_imp.common_channel_promotion_wq; /* * Initialize the promotion workqueue: * - Use 1 HZ to represent the worst-case time for a pending ACT_REQ * to promote. * - The "nr_pending" field will be ignored for this workqueue, but go * ahead and initialize it to 0. */ init_waitqueue_head(&promotion_wq->wq); atomic_set(&promotion_wq->nr_pending, 0); promotion_wq->timeout_per_entry = HZ; /* Initialize the reservation workqueue. */ init_waitqueue_head(&reservation_wq->wq); atomic_set(&reservation_wq->nr_pending, 0); /* * We can quickly calculate the worst-case time for an IMP flip by doing * the following: * 1) Use 1 HZ as the base value. This roughly corresponds to an 1 fps * refresh rate. * 2) For a single IMP flip on head X, we may need to promote BOTH * decreasing and increasing mempool requests for all the other * heads. This is why there's a factor of 2. Keep using the * worst-case promotion time of 1 HZ. */ timeout = HZ; timeout += max(num_heads - 1, 0) * 2 * HZ; /* * The COMMON channel barrier can be held during head enable/disable as * well. Each of these operations should complete within a few * microseconds, but we still need to consider them to approximate our * upper bound. */ timeout = max(timeout, NVDISP_HEAD_ENABLE_DISABLE_TIMEOUT_HZ); reservation_wq->timeout_per_entry = timeout; } static void tegra_nvdisp_init_imp_mc_caps(void) { struct tegra_dc_ext_imp_mc_caps *mc_caps = &g_imp.mc_caps; mc_caps->peak_hubclk_hz = clk_round_rate(hubclk, ULONG_MAX); mc_caps->num_dram_channels = tegra_bwmgr_get_dram_num_channels(); if (tegra_dc_is_t19x()) mc_caps->request_batch_size = 8; else mc_caps->request_batch_size = 32; } static void tegra_nvdisp_init_common_imp_data(void) { struct mrq_emc_dvfs_latency_response *emc_dvfs_table = &g_imp.emc_dvfs_table; uint32_t cur_max_latency = 0; int i; INIT_LIST_HEAD(&g_imp.imp_settings_queue); tegra_nvdisp_init_imp_wqs(); tegra_bpmp_send_receive(MRQ_EMC_DVFS_LATENCY, NULL, 0, emc_dvfs_table, sizeof(*emc_dvfs_table)); for (i = emc_dvfs_table->num_pairs - 1; i >= 0; i--) { struct emc_dvfs_latency *dvfs_pair = &emc_dvfs_table->pairs[i]; cur_max_latency = max(dvfs_pair->latency, cur_max_latency); dvfs_pair->latency = cur_max_latency; } tegra_nvdisp_init_imp_mc_caps(); } static u32 _tegra_nvdisp_get_total_mempool_size(struct tegra_dc *dc) { #define MEMPOOL_WIDTH (32) u32 mempool_entries, bytes_per_entry; u32 ihub_capa; ihub_capa = tegra_dc_readl(dc, nvdisp_ihub_capa_r()); mempool_entries = nvdisp_ihub_capa_mempool_entries_v(ihub_capa); bytes_per_entry = (MEMPOOL_WIDTH << nvdisp_ihub_capa_mempool_width_v(ihub_capa)); return mempool_entries * bytes_per_entry; #undef MEMPOOL_WIDTH } static void tegra_nvdisp_init_common_imp_reg_caps(struct tegra_dc *dc) { mutex_lock(&tegra_nvdisp_lock); if (unlikely(!g_imp.reg_caps_initialized)) { g_imp.mc_caps.total_mempool_size_bytes = _tegra_nvdisp_get_total_mempool_size(dc); g_imp.reg_caps_initialized = true; } mutex_unlock(&tegra_nvdisp_lock); } static int _tegra_nvdisp_init_once(struct tegra_dc *dc) { int ret = 0; int i; char syncpt_name[] = "disp_a"; ret = tegra_nvdisp_reset_prepare(dc); if (ret) return ret; /* Get the nvdisplay_hub and nvdisplay_disp clock and enable * it by default. Change the rates based on requirement later */ hubclk = tegra_disp_clk_get(&dc->ndev->dev, "nvdisplayhub"); if (IS_ERR_OR_NULL(hubclk)) { dev_err(&dc->ndev->dev, "can't get display hub clock\n"); ret = -ENOENT; goto INIT_EXIT; } compclk = tegra_disp_clk_get(&dc->ndev->dev, "nvdisplay_disp"); if (IS_ERR_OR_NULL(compclk)) { dev_err(&dc->ndev->dev, "can't get display comp clock\n"); ret = -ENOENT; goto INIT_CLK_ERR; } /* Init sycpt ids */ dc->valid_windows = 0x3f; /* Assign all windows to this head */ for (i = 0; i < tegra_dc_get_numof_dispwindows(); ++i, ++syncpt_name[5]) { struct tegra_dc_win *win = tegra_dc_get_window(dc, i); win->idx = i; win->syncpt.id = nvhost_get_syncpt_client_managed(dc->ndev, syncpt_name); /* allocate input LUT memory and assign to HW */ ret = nvdisp_alloc_input_lut(dc, win, true); if (ret) goto INIT_LUT_ERR; tegra_dc_init_nvdisp_lut_defaults(&win->nvdisp_lut); win->color_expand_enable = true; win->clamp_before_blend = true; win->force_user_csc = false; win->force_user_degamma = false; } ret = _tegra_nvdisp_init_pd_table(dc); if (ret) goto INIT_PD_ERR; #ifdef CONFIG_TEGRA_ISOMGR /* * Register the IHUB bw client. The bwmgr id that we pass in * must be different from the one that's internally mapped to * the ISO client id. */ ret = tegra_nvdisp_bandwidth_register(TEGRA_ISO_CLIENT_DISP_0, TEGRA_BWMGR_CLIENT_DISP1); if (ret) { dev_err(&dc->ndev->dev, "failed to register ihub bw client\n"); goto BW_REG_ERR; } #endif tegra_nvdisp_init_common_imp_data(); tegra_nvdisp_crc_region_init(); dc->valid_windows = 0; nvdisp_common_init_done = true; goto INIT_EXIT; #ifdef CONFIG_TEGRA_ISOMGR BW_REG_ERR: _tegra_nvdisp_bandwidth_unregister(); #endif INIT_PD_ERR: _tegra_nvdisp_destroy_pd_table(dc); INIT_LUT_ERR: for (i = 0; i < tegra_dc_get_numof_dispwindows(); ++i) { struct tegra_dc_nvdisp_lut *nvdisp_lut; struct tegra_dc_win *win = tegra_dc_get_window(dc, i); /* Free the memory for Input LUT & fb LUT*/ nvdisp_lut = &win->nvdisp_lut; if (nvdisp_lut->rgb) dma_free_coherent(&dc->ndev->dev, nvdisp_lut->size, (void *)nvdisp_lut->rgb, nvdisp_lut->phy_addr); } INIT_CLK_ERR: if (!IS_ERR_OR_NULL(hubclk)) tegra_disp_clk_put(&dc->ndev->dev, hubclk); if (!IS_ERR_OR_NULL(compclk)) tegra_disp_clk_put(&dc->ndev->dev, compclk); INIT_EXIT: return ret; } static inline bool tegra_nvdisp_is_lpf_required(struct tegra_dc *dc) { if (dc->mode.vmode & FB_VMODE_BYPASS) return false; return ((dc->mode.vmode & FB_VMODE_Y422) || tegra_dc_is_yuv420_8bpc(&dc->mode)); } void tegra_nvdisp_set_chroma_lpf(struct tegra_dc *dc) { /* if color fmt is yuv_422 and postcomp support yuv422 * enable chroma lpf by default */ u32 postcomp_capa = tegra_dc_readl(dc, nvdisp_postcomp_capa_r()); u32 chroma_lpf = tegra_dc_readl(dc, nvdisp_procamp_r()); chroma_lpf &= ~nvdisp_procamp_chroma_lpf_enable_f(); if (tegra_nvdisp_is_lpf_required(dc) && nvdisp_postcomp_capa_is_yuv422_enable_v(postcomp_capa)) chroma_lpf |= nvdisp_procamp_chroma_lpf_enable_f(); tegra_dc_writel(dc, chroma_lpf, nvdisp_procamp_r()); } static int _tegra_nvdisp_set_ec_output_lut(struct tegra_dc *dc, struct tegra_dc_mode *mode) { struct tegra_dc_nvdisp_lut *nvdisp_lut = &dc->nvdisp_postcomp_lut; if (!nvdisp_lut) return -ENOMEM; if (mode->vmode & FB_VMODE_SET_YUV_MASK) { if (mode->vmode & FB_VMODE_Y24 || mode->vmode & FB_VMODE_Y30) { nvdisp_copy_output_lut(nvdisp_lut->rgb, yuv8_10bpc_regamma_lut, NVDISP_OUTPUT_LUT_SIZE); } else if (mode->vmode & FB_VMODE_Y36) { nvdisp_copy_output_lut(nvdisp_lut->rgb, yuv12bpc_regamma_lut, NVDISP_OUTPUT_LUT_SIZE); } } return 0; } void tegra_nvdisp_set_ocsc(struct tegra_dc *dc, struct tegra_dc_mode *mode) { u32 csc2_control; /* If mode is not dirty, then set user cached csc2 values */ if (!dc->mode_dirty) { csc2_control = dc->cached_settings.csc2_control; goto write_reg; } if (mode->vmode & FB_VMODE_BYPASS) { /* Use rgb ocsc and disable range scaling for yuv bypass. */ csc2_control = nvdisp_csc2_control_output_color_sel_rgb_f() | nvdisp_csc2_control_limit_rgb_disable_f(); goto update_cache; } if (!IS_RGB(mode->vmode)) { u32 ec = mode->vmode & (FB_VMODE_EC_MASK & ~FB_VMODE_EC_ENABLE); switch (ec) { case FB_VMODE_EC_ADOBE_YCC601: case FB_VMODE_EC_SYCC601: case FB_VMODE_EC_XVYCC601: csc2_control = nvdisp_csc2_control_output_color_sel_y601_f(); break; case FB_VMODE_EC_XVYCC709: default: csc2_control = nvdisp_csc2_control_output_color_sel_y709_f(); break; case FB_VMODE_EC_BT2020_CYCC: case FB_VMODE_EC_BT2020_YCC_RGB: csc2_control = nvdisp_csc2_control_output_color_sel_y2020_f(); break; } } else { csc2_control = nvdisp_csc2_control_output_color_sel_rgb_f(); } if (dc->mode.vmode & FB_VMODE_LIMITED_RANGE) csc2_control |= nvdisp_csc2_control_limit_rgb_enable_f(); else csc2_control |= nvdisp_csc2_control_limit_rgb_disable_f(); update_cache: /* Set user data cache */ dc->cached_settings.csc2_control = csc2_control; write_reg: tegra_dc_writel(dc, csc2_control, nvdisp_csc2_control_r()); } static inline void tegra_nvdisp_set_rg_unstall(struct tegra_dc *dc) { if (tegra_dc_is_t19x()) tegra_nvdisp_set_rg_unstall_t19x(dc); } int tegra_nvdisp_program_mode(struct tegra_dc *dc, struct tegra_dc_mode *mode) { unsigned long v_back_porch; unsigned long v_front_porch; unsigned long v_sync_width; unsigned long v_active; if (!dc->mode.pclk) return 0; if (dc->out_ops && dc->out_ops->modeset_notifier) dc->out_ops->modeset_notifier(dc); v_back_porch = mode->v_back_porch; v_front_porch = mode->v_front_porch; v_sync_width = mode->v_sync_width; v_active = mode->v_active; if (mode->vmode == FB_VMODE_INTERLACED) { v_back_porch /= 2; v_front_porch /= 2; v_sync_width /= 2; v_active /= 2; } tegra_dc_get(dc); tegra_dc_program_bandwidth(dc, true); tegra_dc_writel(dc, nvdisp_sync_width_h_f(mode->h_sync_width) | nvdisp_sync_width_v_f(v_sync_width), nvdisp_sync_width_r()); tegra_dc_writel(dc, nvdisp_back_porch_h_f(mode->h_back_porch) | nvdisp_back_porch_v_f(v_back_porch), nvdisp_back_porch_r()); tegra_dc_writel(dc, nvdisp_front_porch_h_f(mode->h_front_porch) | nvdisp_front_porch_v_f(v_front_porch), nvdisp_front_porch_r()); tegra_dc_writel(dc, nvdisp_active_h_f(mode->h_active) | nvdisp_active_v_f(v_active), nvdisp_active_r()); if (mode->vmode == FB_VMODE_INTERLACED) tegra_dc_writel(dc, INTERLACE_MODE_ENABLE | INTERLACE_START_FIELD_1 | INTERLACE_STATUS_FIELD_1, nvdisp_interlace_ctl_r()); else tegra_dc_writel(dc, INTERLACE_MODE_DISABLE, nvdisp_interlace_ctl_r()); if (mode->vmode == FB_VMODE_INTERLACED) { tegra_dc_writel(dc, nvdisp_interlace_fld2_width_v_f(v_sync_width), nvdisp_interlace_fld2_width_r()); tegra_dc_writel(dc, nvdisp_interlace_fld2_bporch_v_f(v_back_porch + 1), nvdisp_interlace_fld2_bporch_r()); tegra_dc_writel(dc, nvdisp_interlace_fld2_active_v_f(v_active), nvdisp_interlace_fld2_active_r()); tegra_dc_writel(dc, nvdisp_interlace_fld2_fporch_v_f(v_front_porch), nvdisp_interlace_fld2_fporch_r()); } /* TODO: MIPI/CRT/HDMI clock cals */ /* TODO: confirm shift clock still exists in T186 */ if (dc->mode.pclk != mode->pclk) pr_info("Redo Clock pclk 0x%x != dc-pclk 0x%x\n", mode->pclk, dc->mode.pclk); tegra_nvdisp_mode_set_background_color(dc); tegra_nvdisp_set_ocsc(dc, mode); _tegra_nvdisp_set_ec_output_lut(dc, mode); tegra_nvdisp_set_chroma_lpf(dc); tegra_nvdisp_set_rg_unstall(dc); /* general-update */ tegra_nvdisp_activate_general_channel(dc); #ifdef CONFIG_SWITCH switch_set_state(&dc->modeset_switch, (mode->h_active << 16) | mode->v_active); #endif if (dc->mode_dirty) memcpy(&dc->cached_mode, &dc->mode, sizeof(dc->mode)); tegra_dc_put(dc); dc->mode_dirty = false; trace_display_mode(dc, &dc->mode); tegra_dc_ext_process_modechange(dc->ndev->id); tegra_dc_client_handle_event(dc, NOTIFY_MODESET_EVENT); return 0; } int tegra_nvdisp_init(struct tegra_dc *dc) { char rst_name[6]; int err = 0; char syncpt_name[32]; mutex_lock(&tegra_nvdisp_lock); /* Only need init once no matter how many dc objects */ if (!nvdisp_common_init_done) { err = _tegra_nvdisp_init_once(dc); if (err) { mutex_unlock(&tegra_nvdisp_lock); return err; } } mutex_unlock(&tegra_nvdisp_lock); /*Lut alloc is needed per dc */ if (!dc->fb_nvdisp_lut.rgb) { if (nvdisp_alloc_input_lut(dc, NULL, false)) return -ENOMEM; } /* Output LUT is needed per dc */ if (!(dc->nvdisp_postcomp_lut.rgb)) { if (nvdisp_alloc_output_lut(dc)) return -ENOMEM; } /* Set the valid windows as per mask */ dc->valid_windows = dc->pdata->win_mask; /* Assign first valid window as fb window */ if (dc->pdata->fb->win == TEGRA_FB_WIN_INVALID) { int i; for_each_set_bit(i, &dc->valid_windows, tegra_dc_get_numof_dispwindows()) { dc->pdata->fb->win = i; break; } } /* Allocate a syncpoint for vblank on each head */ snprintf(syncpt_name, sizeof(syncpt_name), "vblank%u", dc->ctrl_num); dc->vblank_syncpt = nvhost_get_syncpt_client_managed(dc->ndev, syncpt_name); dev_info(&dc->ndev->dev, "vblank syncpt # %d for dc %d\n", dc->vblank_syncpt, dc->ctrl_num); /* Allocate a syncpoint for vpulse3 on each head */ snprintf(syncpt_name, sizeof(syncpt_name), "disp%u.vpulse3", dc->ctrl_num); dc->vpulse3_syncpt = nvhost_get_syncpt_client_managed(dc->ndev, syncpt_name); dev_info(&dc->ndev->dev, "vpulse3 syncpt # %d for dc %d\n", dc->vpulse3_syncpt, dc->ctrl_num); #ifdef CONFIG_TEGRA_ISOMGR /* Save reference to isohub bw info */ tegra_nvdisp_bandwidth_attach(dc); #endif if (tegra_bpmp_running()) { snprintf(rst_name, sizeof(rst_name), "head%u", dc->ctrl_num); dc->rst = devm_reset_control_get(&dc->ndev->dev, rst_name); if (IS_ERR(dc->rst)) { dev_err(&dc->ndev->dev,"Unable to get %s reset\n", rst_name); return PTR_ERR(dc->rst); } } dc->parent_clk_safe = tegra_disp_clk_get(&dc->ndev->dev, "pllp_display"); if (IS_ERR_OR_NULL(dc->parent_clk_safe)) { dev_err(&dc->ndev->dev, "can't get pllp_display\n"); err = -ENOENT; } return err; } static int tegra_nvdisp_set_control_t18x(struct tegra_dc *dc) { u32 reg, protocol; bool use_sor = false; if ((dc->out->type == TEGRA_DC_OUT_DSI) || (dc->out->type == TEGRA_DC_OUT_FAKE_DSIA) || (dc->out->type == TEGRA_DC_OUT_FAKE_DSIB) || (dc->out->type == TEGRA_DC_OUT_FAKE_DSI_GANGED)) { protocol = nvdisp_dsi_control_protocol_dsia_f(); reg = nvdisp_dsi_control_r(); } else if (dc->out->type == TEGRA_DC_OUT_HDMI) { /* sor1 in the function name is irrelevant */ protocol = nvdisp_sor1_control_protocol_tmdsa_f(); use_sor = true; } else if ((dc->out->type == TEGRA_DC_OUT_DP) || (dc->out->type == TEGRA_DC_OUT_FAKE_DP)) { /* sor in the function name is irrelevant */ protocol = nvdisp_sor_control_protocol_dpa_f(); use_sor = true; } else { dev_err(&dc->ndev->dev, "%s: unsupported out_type=%d\n", __func__, dc->out->type); return -EINVAL; } if (use_sor) { switch (dc->out_ops->get_connector_instance(dc)) { case 0: reg = nvdisp_sor_control_r(); break; case 1: reg = nvdisp_sor1_control_r(); break; default: pr_err("%s: invalid sor_num:%d\n", __func__, dc->out_ops->get_connector_instance(dc)); return -ENODEV; } } tegra_dc_writel(dc, protocol, reg); tegra_dc_enable_general_act(dc); return 0; } static int tegra_nvdisp_head_init(struct tegra_dc *dc) { int ret = 0; u32 int_enable; u32 int_mask; u32 i, val; /* Init syncpt */ tegra_dc_writel(dc, nvdisp_incr_syncpt_cntrl_no_stall_f(1), nvdisp_incr_syncpt_cntrl_r()); tegra_dc_writel(dc, nvdisp_cont_syncpt_vsync_en_enable_f() | nvdisp_cont_syncpt_vsync_indx_f(dc->vblank_syncpt), nvdisp_cont_syncpt_vsync_r()); /* Init interrupts */ /* Setting Int type. EDGE for most, LEVEL for UF related */ tegra_dc_writel(dc, 0x3C000000, nvdisp_int_type_r()); /* Setting all the Int polarity to high */ tegra_dc_writel(dc, 0x3D8010F6, nvdisp_int_polarity_r()); /* enable interrupts for vblank, frame_end and underflows */ int_enable = nvdisp_cmd_int_status_frame_end_f(1) | nvdisp_cmd_int_status_v_blank_f(1) | nvdisp_cmd_int_status_uf_f(1) | nvdisp_cmd_int_status_sd3_f(1); /* for panels with one-shot mode enable tearing effect interrupt */ if (dc->out->flags & TEGRA_DC_OUT_ONE_SHOT_MODE) int_enable |= MSF_INT; /* Todo: also need to enable interrupts for SD3, DSC etc */ tegra_dc_writel(dc, int_enable, nvdisp_cmd_int_enable_r()); int_mask = nvdisp_cmd_int_status_uf_f(1); tegra_dc_writel(dc, int_mask, nvdisp_cmd_int_mask_r()); tegra_dc_writel(dc, nvdisp_state_access_write_mux_assembly_f() | nvdisp_state_access_read_mux_active_f(), nvdisp_state_access_r()); /* YUV bypass flag is set during flips, but we need to check bypass * flag here for YUV modes so that the output CSC and chroma LPF blocks * are correctly programmed during modeset for YUV bypass modes. * Else, momentary screen corruption can be observed during modeset. */ if (dc->mode.pclk) dc->yuv_bypass = (dc->mode.vmode & FB_VMODE_SET_YUV_MASK) && (dc->mode.vmode & FB_VMODE_BYPASS); for_each_set_bit(i, &dc->valid_windows, tegra_dc_get_numof_dispwindows()) { struct tegra_dc_win *win = tegra_dc_get_window(dc, i); /* cache window specific default user data */ win->cached_settings.clamp_before_blend = true; win->cached_settings.color_expand_enable = true; BUG_ON(!win); /* refuse to operate on invalid syncpts */ if (WARN_ON(win->syncpt.id == NVSYNCPT_INVALID)) continue; if (!nvhost_syncpt_read_ext_check(dc->ndev, win->syncpt.id, &val)) win->syncpt.min = win->syncpt.max = val; } dc->crc_pending = false; /* set mode */ tegra_nvdisp_program_mode(dc, &dc->mode); if (tegra_dc_is_t18x()) ret = tegra_nvdisp_set_control_t18x(dc); else if (tegra_dc_is_t19x()) ret = tegra_nvdisp_set_control_t19x(dc); if (ret) { dev_err(&dc->ndev->dev, "%s: error:%d in set_control\n", __func__, ret); goto exit; } tegra_nvdisp_set_color_control(dc); /* Enable Vpulse3 scanline signal */ tegra_dc_writel(dc, nvdisp_disp_signal_option_v_pulse3_enable_f(), nvdisp_disp_signal_option_r()); tegra_dc_enable_general_act(dc); exit: return ret; } static struct tegra_nvdisp_imp_head_settings *find_imp_head_by_ctrl_num( struct tegra_nvdisp_imp_settings *imp_settings, struct tegra_dc *dc) { int i; for (i = 0; i < imp_settings->num_heads; i++) { struct tegra_nvdisp_imp_head_settings *head_settings; head_settings = &imp_settings->head_settings[i]; if (head_settings->entries.ctrl_num == dc->ctrl_num) return head_settings; } return NULL; } static inline void tegra_nvdisp_program_common_win_batch_size( struct tegra_dc *dc) { if (tegra_dc_is_t19x()) tegra_nvdisp_program_common_win_batch_size_t19x(dc); } static void tegra_nvdisp_postcomp_imp_init(struct tegra_dc *dc) { struct nvdisp_imp_table *imp_table; struct tegra_nvdisp_imp_settings *boot_setting; struct tegra_nvdisp_imp_head_settings *head_settings; struct tegra_dc_ext_nvdisp_imp_head_entries *head_entries; bool any_dc_enabled = false; int i; for (i = 0; i < tegra_dc_get_numof_dispheads(); i++) { struct tegra_dc *other_dc = tegra_dc_get_dc(i); if (other_dc && other_dc->enabled) { any_dc_enabled = true; break; } } /* * Use any_dc_enabled to ensure that the common win batch request size * is only programmed by the first head that comes up. */ if (!any_dc_enabled) tegra_nvdisp_program_common_win_batch_size(dc); if (!tegra_platform_is_silicon()) return; imp_table = tegra_dc_common_get_imp_table(); if (!imp_table) return; /* * Use any dc_enabled to ensure that the common fetch metering is only * initialized by the first head that comes up. */ boot_setting = imp_table->boot_setting; if (!any_dc_enabled) tegra_nvdisp_program_common_fetch_meter(dc, boot_setting->global_entries.total_curs_fetch_slots, boot_setting->global_entries.total_win_fetch_slots); /* * Program default ihub values for the cursor on this head. This should * be done before enabling the output LUT since it also uses those same * values. There shouldn't be any need to disable ihub latency events * since the cursor pipe isn't active at this point in time. */ head_settings = find_imp_head_by_ctrl_num(boot_setting, dc); head_entries = &head_settings->entries; tegra_nvdisp_program_imp_curs_entries(dc, head_entries->curs_fetch_slots, head_entries->curs_pipe_meter, head_entries->curs_dvfs_watermark, head_entries->curs_mempool_entries, dc->ctrl_num); } static int tegra_nvdisp_postcomp_init(struct tegra_dc *dc) { struct tegra_dc_nvdisp_lut *nvdisp_lut = NULL; u32 update_mask = 0; u32 act_req_mask = 0; tegra_nvdisp_postcomp_imp_init(dc); update_mask |= nvdisp_cmd_state_ctrl_common_act_update_enable_f(); act_req_mask |= nvdisp_cmd_state_ctrl_common_act_req_enable_f(); /* * Set the LUT address in the HW register. Enable the default sRGB_LUT. * Replace this with the LUT derived from panel characterization through * DT. */ nvdisp_lut = &dc->nvdisp_postcomp_lut; if (dc->cmu_enabled) { tegra_nvdisp_program_output_lut(dc, nvdisp_lut); tegra_nvdisp_set_color_control(dc); act_req_mask |= nvdisp_cmd_state_ctrl_general_act_req_enable_f(); } if (tegra_dc_enable_update_and_act(dc, update_mask, act_req_mask)) dev_err(&dc->ndev->dev, "timeout waiting for postcomp init state to promote\n"); return 0; } static int tegra_nvdisp_rg_init(struct tegra_dc *dc) { return 0; } static int tegra_nvdisp_cursor_init(struct tegra_dc *dc) { return 0; } /* * Moves all the win entries specified in the winmask to the front of the * head_settings->win_entries array. */ static u8 swap_imp_boot_wins( struct tegra_nvdisp_imp_head_settings *head_settings, unsigned long winmask) { u8 num_wins = 0; int i; for (i = 0; i < head_settings->num_wins; i++) { struct tegra_dc_ext_nvdisp_imp_win_entries *win_entry; win_entry = &head_settings->win_entries[i]; if ((1 << win_entry->id) & winmask) { struct tegra_dc_ext_nvdisp_imp_win_entries tmp; tmp = head_settings->win_entries[num_wins]; head_settings->win_entries[num_wins++] = *win_entry; head_settings->win_entries[i] = tmp; } } return num_wins; } static void tegra_nvdisp_dc_wins_imp_init(struct tegra_dc *dc) { struct nvdisp_imp_table *imp_table; struct tegra_dc_win *win; int max_num_wins = tegra_dc_get_numof_dispwindows(); int i; /* * For non-silicon platforms, assign a 1:1 thread group mapping and get * out. Dynamic IMP won't be enabled on these platforms, which means * that the reset values for the other IHUB registers should be fine. */ if (!tegra_platform_is_silicon()) { for_each_set_bit(i, &dc->pdata->win_mask, max_num_wins) { win = tegra_dc_get_window(dc, i); if (!win) continue; nvdisp_win_write(win, win_ihub_thread_group_num_f(i) | win_ihub_thread_group_enable_yes_f(), win_ihub_thread_group_r()); } return; } imp_table = tegra_dc_common_get_imp_table(); if (imp_table) { struct tegra_nvdisp_imp_settings *boot_setting; struct tegra_nvdisp_imp_head_settings *head_settings; u8 cur_win_num; boot_setting = imp_table->boot_setting; head_settings = find_imp_head_by_ctrl_num(boot_setting, dc); cur_win_num = head_settings->num_wins; head_settings->num_wins = swap_imp_boot_wins(head_settings, dc->pdata->win_mask); for (i = 0; i < head_settings->num_wins; i++) { struct tegra_dc_ext_nvdisp_imp_win_entries *win_entry; u32 tg = win_ihub_thread_group_enable_no_f(); win_entry = &head_settings->win_entries[i]; tegra_nvdisp_program_imp_win_entries(dc, win_entry->thread_group, win_entry->fetch_slots, win_entry->pipe_meter, win_entry->dvfs_watermark, win_entry->mempool_entries, win_entry->id, dc->ctrl_num); win = tegra_dc_get_window(dc, win_entry->id); if (win_entry->thread_group >= 0) tg = win_ihub_thread_group_enable_yes_f() | win_ihub_thread_group_num_f( win_entry->thread_group); nvdisp_win_write(win, tg, win_ihub_thread_group_r()); } head_settings->num_wins = cur_win_num; } } static int tegra_nvdisp_assign_dc_wins(struct tegra_dc *dc) { u32 update_mask = nvdisp_cmd_state_ctrl_common_act_update_enable_f(); u32 act_req_mask = nvdisp_cmd_state_ctrl_common_act_req_enable_f(); int num_wins = tegra_dc_get_numof_dispwindows(); int idx = 0, ret = 0; int i = -1; mutex_lock(&tegra_nvdisp_lock); /* Assign windows to this head. */ for_each_set_bit(idx, &dc->pdata->win_mask, num_wins) { if (tegra_nvdisp_assign_win(dc, idx)) { dev_err(&dc->ndev->dev, "failed to assign window %d\n", idx); } else { dev_dbg(&dc->ndev->dev, "Window %d assigned to head %d\n", idx, dc->ctrl_num); update_mask |= nvdisp_cmd_state_ctrl_win_a_update_enable_f() << idx; act_req_mask |= nvdisp_cmd_state_ctrl_a_act_req_enable_f() << idx; if (i == -1) i = idx; } } tegra_nvdisp_dc_wins_imp_init(dc); /* Wait for COMMON_ACT_REQ to complete or time out. */ if (tegra_dc_enable_update_and_act(dc, update_mask, act_req_mask)) { dev_err(&dc->ndev->dev, "timeout waiting for win assignments to promote\n"); ret = -EINVAL; } mutex_unlock(&tegra_nvdisp_lock); /* Set the fb_index on changing from a zero winmask to a valid one. */ if ((dc->pdata->fb->win == -1) && dc->pdata->win_mask) { tegra_fb_set_win_index(dc, dc->pdata->win_mask); dc->pdata->fb->win = i; } return ret; } int tegra_nvdisp_head_disable(struct tegra_dc *dc) { int idx, ret = 0; struct tegra_dc *dc_other = NULL; /* * Disable the DVFS watermarks for the cursor and windows owned by this * head. * * The relevant cursor and windows should have already been disabled * prior to tegra_nvdisp_head_disable() being called, so it should be * safe to disable their watermarks, even though the watermark registers * aren't buffered at all. */ tegra_nvdisp_program_curs_dvfs_watermark(dc, 0x0); for_each_set_bit(idx, &dc->pdata->win_mask, tegra_dc_get_numof_dispwindows()) { struct tegra_dc_win *win = tegra_dc_get_window(dc, idx); if (!win || win->dc != dc) continue; /* * The window owner might have already been set to NONE, so * program the assembly state accordingly so that we can * actually write the register. * * The window owner will be categorically set to NONE during the * tegra_nvdisp_detach_win() call right below. */ nvdisp_win_write(win, dc->ctrl_num, win_set_control_r()); tegra_nvdisp_program_win_dvfs_watermark(win, 0x0); } /* Detach windows from the head */ for_each_set_bit(idx, &dc->pdata->win_mask, tegra_dc_get_numof_dispwindows()) { if (tegra_nvdisp_detach_win(dc, idx)) dev_err(&dc->ndev->dev, "failed to detach window %d\n", idx); else dev_dbg(&dc->ndev->dev, "Window %d detached from head %d\n", idx, dc->ctrl_num); } /* Set comp clock to different pclk since dc->clk will be disabled */ mutex_lock(&tegra_nvdisp_lock); dc->comp_clk_inuse = false; mutex_unlock(&tegra_nvdisp_lock); tegra_nvdisp_set_compclk(dc); if (dc->out->dsc_en) tegra_dc_en_dis_dsc(dc, false); /* Disable DC clock */ tegra_disp_clk_disable_unprepare(dc->clk); ret = clk_set_parent(dc->clk, dc->parent_clk_safe); if (ret) dev_err(&dc->ndev->dev, "can't set parent_clk_safe for dc->clk\n"); /* check if any of head is using hub clock */ mutex_lock(&tegra_nvdisp_lock); for (idx = 0; idx < tegra_dc_get_numof_dispheads(); idx++) { dc_other = tegra_dc_get_dc(idx); if (dc_other && dc_other->comp_clk_inuse) break; } /* disable hub clock if none of the heads is using it and clear bw */ if (idx == tegra_dc_get_numof_dispheads() && hubclk_already_on) { tegra_nvdisp_clear_bandwidth(dc); tegra_disp_clk_disable_unprepare(hubclk); hubclk_already_on = false; } mutex_unlock(&tegra_nvdisp_lock); return ret; } int tegra_nvdisp_head_enable(struct tegra_dc *dc) { int i; int res; int pclk = 0, ret = 0; struct clk *parent_clk = NULL; if (WARN_ON(!dc || !dc->out || !dc->out_ops)) return false; tegra_dc_unpowergate_locked(dc); /* set clock status to inuse */ mutex_lock(&tegra_nvdisp_lock); dc->comp_clk_inuse = true; /* turn on hub clock and init bw */ if (!hubclk_already_on) { tegra_nvdisp_init_bandwidth(dc); tegra_disp_clk_prepare_enable(hubclk); hubclk_already_on = true; } mutex_unlock(&tegra_nvdisp_lock); pr_debug(" rate get on hub %ld\n", clk_get_rate(hubclk)); /* Enable OR -- need to enable the connection first */ if (dc->out->enable) dc->out->enable(&dc->ndev->dev); /* Setting DC clocks, DC, COMPCLK * Set maximum of DC clock for COMPCLK */ if (dc->out->type == TEGRA_DC_OUT_DSI) { parent_clk = tegra_disp_clk_get(&dc->ndev->dev, "pll_d_out1"); } else { parent_clk = tegra_disp_clk_get(&dc->ndev->dev, dc->out->parent_clk); pr_info("Parent Clock set for DC %s\n", dc->out->parent_clk); } if (IS_ERR_OR_NULL(parent_clk)) { dev_err(&dc->ndev->dev, "Failed to get DC Parent clock\n"); ret = -ENOENT; return ret; /*TODO: Add proper cleanup later */ } /* Set parent for DC clock */ clk_set_parent(dc->clk, parent_clk); /* Set rate on DC same as pclk */ if (!dc->initialized) clk_set_rate(dc->clk, dc->mode.pclk); if (dc->out_ops->setup_clk) pclk = dc->out_ops->setup_clk(dc, dc->clk); /* Enable DC clock */ tegra_disp_clk_prepare_enable(dc->clk); pr_debug(" dc clk %ld\n", clk_get_rate(dc->clk)); tegra_nvdisp_set_compclk(dc); tegra_dc_get(dc); /* Deassert the dc reset */ if (tegra_bpmp_running()) { res = reset_control_deassert(dc->rst); if (res) { dev_err(&dc->ndev->dev, "Unable to deassert dc %d\n", dc->ctrl_num); return res; } tegra_nvdisp_wgrp_reset_deassert(dc); } /* Mask interrupts during init */ tegra_dc_writel(dc, 0, DC_CMD_INT_MASK); enable_irq(dc->irq); /* * These IMP caps only need to be initialized once. However, we're * putting this function call here during head enable in order to avoid * separate powergate/rst handling during init. */ tegra_nvdisp_init_common_imp_reg_caps(dc); res = tegra_nvdisp_head_init(dc); res |= tegra_nvdisp_postcomp_init(dc); res |= tegra_nvdisp_rg_init(dc); res |= tegra_nvdisp_cursor_init(dc); res |= tegra_nvdisp_assign_dc_wins(dc); if (res) { dev_err(&dc->ndev->dev, "%s, failed head enable\n", __func__); goto failed_enable; } tegra_dc_dsc_init(dc); if (dc->out->dsc_en) tegra_dc_en_dis_dsc(dc, true); if (dc->out_ops && dc->out_ops->enable) dc->out_ops->enable(dc); /* force a full blending update */ for (i = 0; i < tegra_dc_get_numof_dispwindows(); i++) dc->blend.z[i] = -1; tegra_dc_ext_enable(dc->ext); trace_display_enable(dc); if (dc->out->postpoweron) dc->out->postpoweron(&dc->ndev->dev); if (dc->out_ops && dc->out_ops->postpoweron) dc->out_ops->postpoweron(dc); tegra_log_resume_time(); /* * We will need to reinitialize the display the next time panel * is enabled. */ dc->out->flags &= ~TEGRA_DC_OUT_INITIALIZED_MODE; /* Enable RG underflow logging */ tegra_dc_writel(dc, nvdisp_rg_underflow_enable_enable_f() | nvdisp_rg_underflow_mode_red_f(), nvdisp_rg_underflow_r()); tegra_dc_put(dc); return 0; failed_enable: tegra_dc_writel(dc, 0, DC_CMD_INT_MASK); disable_irq_nosync(dc->irq); tegra_dc_clear_bandwidth(dc); if (dc->out && dc->out->disable) dc->out->disable(&dc->ndev->dev); tegra_dc_put(dc); /* TODO: disable DC clock */ return -EINVAL; } void tegra_nvdisp_set_vrr_mode(struct tegra_dc *dc) { tegra_dc_writel(dc, nvdisp_display_rate_min_refresh_enable_f(1) | nvdisp_display_rate_min_refresh_interval_f( (u32)(div_s64(dc->frametime_ns, NSEC_PER_MICROSEC))), nvdisp_display_rate_r()); tegra_dc_writel(dc, nvdisp_display_command_control_mode_nc_display_f(), nvdisp_display_command_r()); tegra_dc_writel(dc, nvdisp_cmd_state_ctrl_host_trig_enable_f() , nvdisp_cmd_state_ctrl_r()); } EXPORT_SYMBOL(tegra_nvdisp_set_vrr_mode); void tegra_nvdisp_vrr_work(struct work_struct *work) { int reg_val; int frame_time_elapsed; struct timespec time_now; s64 time_now_us; struct tegra_dc *dc = container_of( to_delayed_work(work), struct tegra_dc, vrr_work); struct tegra_vrr *vrr = dc->out->vrr; mutex_lock(&dc->lock); tegra_dc_get(dc); getnstimeofday(&time_now); time_now_us = (s64)time_now.tv_sec * 1000000 + time_now.tv_nsec / 1000; frame_time_elapsed = time_now_us - vrr->curr_frame_us; if (frame_time_elapsed < (vrr->frame_len_max - MIN_FRAME_INTERVAL)) { reg_val = tegra_dc_readl(dc, nvdisp_cmd_state_ctrl_r()); reg_val |= nvdisp_cmd_state_ctrl_host_trig_enable_f(); reg_val |= nvdisp_cmd_state_ctrl_general_act_req_enable_f(); tegra_dc_writel(dc, reg_val, nvdisp_cmd_state_ctrl_r()); tegra_dc_readl(dc, nvdisp_cmd_state_ctrl_r()); /* flush */ } tegra_dc_put(dc); mutex_unlock(&dc->lock); return; } EXPORT_SYMBOL(tegra_nvdisp_vrr_work); void tegra_nvdisp_stop_display(struct tegra_dc *dc) { tegra_dc_writel(dc, nvdisp_display_command_control_mode_stop_f(), nvdisp_display_command_r()); } EXPORT_SYMBOL(tegra_nvdisp_stop_display); void tegra_nvdisp_sysfs_enable_crc(struct tegra_dc *dc) { mutex_lock(&dc->lock); tegra_dc_get(dc); tegra_dc_writel(dc, nvdisp_crc_control_enable_enable_f() | nvdisp_crc_control_input_data_active_data_f(), nvdisp_crc_control_r()); tegra_dc_enable_general_act(dc); dc->crc_ref_cnt.legacy = true; tegra_dc_put(dc); mutex_unlock(&dc->lock); /* Register a client of frame_end interrupt */ tegra_dc_config_frame_end_intr(dc, true); } void tegra_nvdisp_sysfs_disable_crc(struct tegra_dc *dc) { /* Unregister a client of frame_end interrupt */ tegra_dc_config_frame_end_intr(dc, false); mutex_lock(&dc->lock); tegra_dc_get(dc); tegra_dc_writel(dc, 0x0, nvdisp_crc_control_r()); tegra_dc_enable_general_act(dc); dc->crc_ref_cnt.legacy = false; tegra_dc_put(dc); mutex_unlock(&dc->lock); } u32 tegra_nvdisp_sysfs_read_rg_crc(struct tegra_dc *dc) { int crc = 0; int val = 0; if (!dc) { pr_err("Failed to get dc: NULL parameter.\n"); goto crc_error; } /* If gated quitely return */ if (tegra_bpmp_running() && !tegra_dc_is_powered(dc)) return 0; #ifdef INIT_COMPLETION INIT_COMPLETION(dc->crc_complete); #else reinit_completion(&dc->crc_complete); #endif if (dc->crc_pending && wait_for_completion_interruptible(&dc->crc_complete)) { pr_err("CRC read interrupted.\n"); goto crc_error; } mutex_lock(&dc->lock); tegra_dc_get(dc); val = tegra_dc_readl(dc, nvdisp_rg_crca_r()); if (val & nvdisp_rg_crca_valid_true_f()) crc = tegra_dc_readl(dc, nvdisp_rg_crcb_r()); /* clear the error bit if set */ if (val & nvdisp_rg_crca_error_true_f()) tegra_dc_writel(dc, nvdisp_rg_crca_error_true_f(), nvdisp_rg_crca_r()); tegra_dc_put(dc); mutex_unlock(&dc->lock); crc_error: return crc; } void tegra_nvdisp_underflow_handler(struct tegra_dc *dc) { u32 reg = tegra_dc_readl(dc, nvdisp_rg_underflow_r()); dc->stats.underflows++; if (dc->underflow_mask & NVDISP_UF_INT) dc->stats.underflow_frames += nvdisp_rg_underflow_frames_uflowed_v(reg); /* Clear the sticky bit and counter */ tegra_dc_writel(dc, nvdisp_rg_underflow_frames_uflowed_rst_trigger_f() | nvdisp_rg_underflow_uflowed_clr_f(), nvdisp_rg_underflow_r()); /* Check whether the reset is done */ reg = tegra_dc_readl(dc, nvdisp_rg_underflow_r()); if (reg & nvdisp_rg_underflow_frames_uflowed_rst_pending_f()) { pr_err("nvdisp_rg_underflow_frames_uflowed_rst is pending\n"); } else { /* Enable RG underflow logging */ tegra_dc_writel(dc, nvdisp_rg_underflow_enable_enable_f() | nvdisp_rg_underflow_mode_red_f(), nvdisp_rg_underflow_r()); } } int tegra_nvdisp_is_powered(struct tegra_dc *dc) { struct tegra_dc_pd_table *pd_table = tegra_dc_get_disp_pd_table(); struct tegra_dc_pd_info *pds = pd_table->pd_entries; u32 cur_head_mask = (1 << dc->ctrl_num); int npower_domains = pd_table->npd; int i, ret = 0; mutex_lock(&pd_table->pd_lock); for (i = 0; i < npower_domains; i++) { struct tegra_dc_pd_info *pd = &pds[i]; if (cur_head_mask & pd->head_mask) { ret = pd->ref_cnt; break; } } mutex_unlock(&pd_table->pd_lock); return ret; } static inline int tegra_nvdisp_handle_pd_enable(struct tegra_dc_pd_info *pd, int ref_cnt_update) { int ret = 0; if (pd->ref_cnt == 0) { struct tegra_dc_pd_clk_info *domain_clks = pd->domain_clks; int nclks = pd->nclks; int i; ret = tegra_unpowergate_partition(pd->pg_id); if (ret) { pr_err("%s: Failed to unpowergate Head%u pd\n", __func__, pd->head_owner); return -EINVAL; } for (i = 0; i < nclks; i++) tegra_disp_clk_prepare_enable(domain_clks[i].clk); pr_info("%s: Unpowergated Head%u pd\n", __func__, pd->head_owner); } pd->ref_cnt += ref_cnt_update; return ret; } static inline int tegra_nvdisp_handle_pd_disable(struct tegra_dc_pd_info *pd, int ref_cnt_update) { int remaining_ref_cnt = pd->ref_cnt - ref_cnt_update; int ret = 0; if (remaining_ref_cnt < 0) { pr_err("%s: Unbalanced ref count for Head%u pd=%d\n", __func__, pd->head_owner, pd->ref_cnt); return -EINVAL; } else if (remaining_ref_cnt == 0) { struct tegra_dc_pd_clk_info *domain_clks = pd->domain_clks; int nclks = pd->nclks; int i; ret = tegra_powergate_partition(pd->pg_id); if (ret) { pr_err("%s: Failed to powergate Head%u pd\n", __func__, pd->head_owner); return -EINVAL; } for (i = 0; i < nclks; i++) tegra_disp_clk_disable_unprepare(domain_clks[i].clk); pr_info("%s: Powergated Head%u pd\n", __func__, pd->head_owner); } pd->ref_cnt = remaining_ref_cnt; return ret; } static int tegra_nvdisp_update_pd_ref_cnts(struct tegra_dc *dc, bool enable) { struct tegra_dc_pd_table *pd_table = tegra_dc_get_disp_pd_table(); struct tegra_dc_pd_info *pds = pd_table->pd_entries; struct tegra_dc_pd_info *pd; u32 cur_head_mask = (1 << dc->ctrl_num); u32 pd_owner; int npower_domains = pd_table->npd; int ret = 0, i; int pd_ref_cnt_updates[npower_domains]; memset(pd_ref_cnt_updates, 0, sizeof(pd_ref_cnt_updates[0]) * npower_domains); for (i = 0; i < npower_domains; i++) { pd = &pds[i]; pd_owner = pd->head_owner; /* * Check whether the given head and/or ANY of its assigned * windows reside in this power domain. Take/release an extra * refcount to the DISP (Head0) power domain if necessary. * There's an implicit dependency here since DISP houses common * logic, such as PFE, IHUB, ORs, etc. */ if ((cur_head_mask & pd->head_mask) || (dc->valid_windows & pd->win_mask)) { pd_ref_cnt_updates[pd_owner]++; if (pd_owner != 0) pd_ref_cnt_updates[0]++; } } mutex_lock(&pd_table->pd_lock); for (i = 0; i < npower_domains; i++) { int ref_cnt_update; int pd_idx = i; /* * If this is a powerGATE request, start iterating backwards * and save DISP for last. */ if (!enable) pd_idx = npower_domains - i - 1; pd = &pds[pd_idx]; pd_owner = pd->head_owner; ref_cnt_update = pd_ref_cnt_updates[pd_owner]; if (ref_cnt_update == 0) continue; if (enable) ret = tegra_nvdisp_handle_pd_enable(pd, ref_cnt_update); else ret = tegra_nvdisp_handle_pd_disable(pd, ref_cnt_update); if (ret) break; } mutex_unlock(&pd_table->pd_lock); return ret; } int tegra_nvdisp_unpowergate_dc(struct tegra_dc *dc) { if (!dc) { pr_err("%s: DC is NULL\n", __func__); return -ENODEV; } return tegra_nvdisp_update_pd_ref_cnts(dc, true); } int tegra_nvdisp_powergate_dc(struct tegra_dc *dc) { if (!dc) { pr_err("%s: DC is NULL\n", __func__); return -ENODEV; } return tegra_nvdisp_update_pd_ref_cnts(dc, false); } static int tegra_nvdisp_set_color_control(struct tegra_dc *dc) { u32 color_control; switch (dc->out->depth) { case 36: color_control = nvdisp_color_ctl_base_color_size_36bits_f(); break; case 30: color_control = nvdisp_color_ctl_base_color_size_30bits_f(); break; case 24: color_control = nvdisp_color_ctl_base_color_size_24bits_f(); break; case 18: color_control = nvdisp_color_ctl_base_color_size_18bits_f(); break; default: color_control = nvdisp_color_ctl_base_color_size_24bits_f(); break; } switch (dc->out->dither) { case TEGRA_DC_UNDEFINED_DITHER: case TEGRA_DC_DISABLE_DITHER: color_control |= nvdisp_color_ctl_dither_ctl_disable_f(); break; case TEGRA_DC_ORDERED_DITHER: color_control |= nvdisp_color_ctl_dither_ctl_ordered_f(); break; case TEGRA_DC_TEMPORAL_DITHER: color_control |= nvdisp_color_ctl_dither_ctl_temporal_f(); break; case TEGRA_DC_ERRACC_DITHER: color_control |= nvdisp_color_ctl_dither_ctl_err_acc_f(); break; default: dev_err(&dc->ndev->dev, "Error: Unsupported dithering mode\n"); } if (dc->cmu_enabled) color_control |= nvdisp_color_ctl_cmu_enable_f(); /* TO DO - dither rotation, dither offset, dither phase */ tegra_dc_writel(dc, color_control, nvdisp_color_ctl_r()); return 0; } void tegra_dc_cache_nvdisp_cmu(struct tegra_dc *dc, struct tegra_dc_nvdisp_cmu *src_cmu) { /* copy the data to DC lut */ memcpy(dc->nvdisp_postcomp_lut.rgb, src_cmu->rgb, dc->nvdisp_postcomp_lut.size); dc->cmu_dirty = true; } static void _tegra_nvdisp_update_cmu(struct tegra_dc *dc, struct tegra_dc_nvdisp_lut *cmu) { dc->cmu_enabled = dc->pdata->cmu_enable; if (!dc->cmu_enabled) return; /* Not disabling the cmu here - will * consider it if there is any corruption on * updating cmu while it is running */ tegra_nvdisp_program_output_lut(dc, cmu); dc->cmu_dirty = false; } int tegra_nvdisp_update_cmu(struct tegra_dc *dc, struct tegra_dc_nvdisp_lut *cmu) { u32 act_req_mask = nvdisp_cmd_state_ctrl_general_act_req_enable_f(); mutex_lock(&dc->lock); if (!dc->enabled) { mutex_unlock(&dc->lock); return 0; } tegra_dc_get(dc); _tegra_nvdisp_update_cmu(dc, cmu); tegra_nvdisp_set_color_control(dc); tegra_dc_writel(dc, act_req_mask, nvdisp_cmd_state_ctrl_r()); tegra_dc_readl(dc, nvdisp_cmd_state_ctrl_r()); if (tegra_dc_in_cmode(dc)) { /* wait for ACT_REQ to complete or time out */ if (tegra_dc_poll_register(dc, nvdisp_cmd_state_ctrl_r(), act_req_mask, 0, 1, NVDISP_TEGRA_POLL_TIMEOUT_MS)) dev_err(&dc->ndev->dev, "dc timeout waiting to clear ACT_REQ, mask:0x%x\n", act_req_mask); } tegra_dc_put(dc); mutex_unlock(&dc->lock); return 0; } EXPORT_SYMBOL(tegra_nvdisp_update_cmu); static inline void tegra_nvdisp_enable_ihub_latency_events(struct tegra_dc *dc) { tegra_dc_writel(dc, tegra_dc_readl(dc, nvdisp_ihub_misc_ctl_r()) | nvdisp_ihub_misc_ctl_latency_event_enable_f(), nvdisp_ihub_misc_ctl_r()); } static inline void tegra_nvdisp_disable_ihub_latency_events(struct tegra_dc *dc) { tegra_dc_writel(dc, tegra_dc_readl(dc, nvdisp_ihub_misc_ctl_r()) & ~nvdisp_ihub_misc_ctl_latency_event_enable_f(), nvdisp_ihub_misc_ctl_r()); } static inline void tegra_nvdisp_program_curs_dvfs_watermark(struct tegra_dc *dc, u32 dvfs_watermark) { if (dvfs_watermark) { tegra_dc_writel(dc, nvdisp_ihub_cursor_latency_ctla_ctl_mode_enable_f() | nvdisp_ihub_cursor_latency_ctla_submode_watermark_f(), nvdisp_ihub_cursor_latency_ctla_r()); tegra_dc_writel(dc, nvdisp_ihub_cursor_latency_ctlb_watermark_f(dvfs_watermark), nvdisp_ihub_cursor_latency_ctlb_r()); } else { /* disable watermark */ tegra_dc_writel(dc, 0x0, nvdisp_ihub_cursor_latency_ctla_r()); tegra_dc_writel(dc, nvdisp_ihub_cursor_latency_ctlb_watermark_f(0x1fffffff), nvdisp_ihub_cursor_latency_ctlb_r()); } } static inline void tegra_nvdisp_program_win_dvfs_watermark( struct tegra_dc_win *win, u32 dvfs_watermark) { if (dvfs_watermark) { nvdisp_win_write(win, win_ihub_latency_ctla_ctl_mode_enable_f() | win_ihub_latency_ctla_submode_watermark_f(), win_ihub_latency_ctla_r()); nvdisp_win_write(win, win_ihub_latency_ctlb_watermark_f(dvfs_watermark), win_ihub_latency_ctlb_r()); } else { /* disable watermark */ nvdisp_win_write(win, 0x0, win_ihub_latency_ctla_r()); nvdisp_win_write(win, win_ihub_latency_ctlb_watermark_f(0x1fffffff), win_ihub_latency_ctlb_r()); } } static inline void tegra_nvdisp_program_common_fetch_meter(struct tegra_dc *dc, u32 curs_slots, u32 win_slots) { tegra_dc_writel(dc, nvdisp_ihub_common_fetch_meter_cursor_slots_f(curs_slots) | nvdisp_ihub_common_fetch_meter_wgrp_slots_f(win_slots), nvdisp_ihub_common_fetch_meter_r()); trace_display_imp_program_global(dc->ctrl_num, curs_slots, win_slots); } static u32 tegra_nvdisp_get_active_curs_pool(struct tegra_dc *dc) { u32 cur_val = 0; if (!dc || !dc->enabled) return cur_val; /* * If either the cursor surface or output LUT are currently enabled on * this head, take the active mempool value into account. Else, assume * there are no mempool entries assigned. * * The reason why output LUT matters is because it shares both the same * precomp pipe and FB request port as the corresponding cursor pipe. * As such, the cursor ihub settings would still be programmed in a case * where cursor is disabled, but output LUT is enabled. */ if (dc->cursor.enabled || dc->cmu_enabled) cur_val = nvdisp_ihub_cursor_pool_config_entries_f( tegra_dc_readl(dc, nvdisp_ihub_cursor_pool_config_r())); return cur_val; } static u32 tegra_nvdisp_get_active_win_pool(struct tegra_dc_win *win) { u32 cur_val = 0; if (!win || !win->dc) return cur_val; /* * If this window is currently enabled, take the active mempool value * into account. Else, treat this window as having no mempool entries. */ if (WIN_IS_ENABLED(win)) cur_val = win_ihub_pool_config_entries_f( nvdisp_win_read(win, win_ihub_pool_config_r())); return cur_val; } static int tegra_nvdisp_get_v_taps_user_info( struct tegra_dc_ext_imp_user_info *info) { u32 num_wins; u32 *win_ids; u32 *in_widths; u32 *out_widths; u32 *v_taps; size_t i, j, win_arr_size; int ret = 0; num_wins = info->num_windows; win_arr_size = num_wins * sizeof(u32); win_ids = kzalloc(win_arr_size, GFP_KERNEL); in_widths = kzalloc(win_arr_size, GFP_KERNEL); out_widths = kzalloc(win_arr_size, GFP_KERNEL); v_taps = kzalloc(win_arr_size, GFP_KERNEL); if (!win_ids || !in_widths || !out_widths || !v_taps) { ret = -ENOMEM; goto v_taps_free_and_ret; } if (copy_from_user(win_ids, info->win_ids, win_arr_size)) { ret = -EFAULT; goto v_taps_free_and_ret; } if (copy_from_user(in_widths, info->in_widths, win_arr_size)) { ret = -EFAULT; goto v_taps_free_and_ret; } if (copy_from_user(out_widths, info->out_widths, win_arr_size)) { ret = -EFAULT; goto v_taps_free_and_ret; } for (i = 0; i < num_wins; i++) { int win_capc, win_cape; int min_width; struct tegra_dc *owner_dc = NULL; struct tegra_dc_win *win = NULL; for (j = 0; j < tegra_dc_get_numof_dispheads(); j++) { owner_dc = tegra_dc_get_dc(j); if (!owner_dc) continue; win = tegra_dc_get_window(owner_dc, win_ids[i]); if (win) break; } if (!win) continue; /* * in_widths[i] has 20 bits integer (MSB) and 12 bits fractional * (LSB) */ win_capc = win->precomp_capc; win_cape = win->precomp_cape; min_width = ((in_widths[i] >> 12) < out_widths[i]) ? in_widths[i] >> 12 : out_widths[i]; if (min_width < win_precomp_wgrp_capc_max_pixels_5tap444_v(win_capc)) v_taps[i] = 5; else /* IMP only accepts 2 or 5 for v taps */ v_taps[i] = 2; } if (copy_to_user(info->v_taps, v_taps, win_arr_size)) { ret = -EFAULT; goto v_taps_free_and_ret; } v_taps_free_and_ret: kfree(win_ids); kfree(in_widths); kfree(out_widths); kfree(v_taps); return ret; } static int cpy_dvfs_pairs_to_user(void __user *ext_dvfs_ptr, u32 num_pairs_requested, u32 *num_pairs_returned) { struct mrq_emc_dvfs_latency_response *dvfs_table = &g_imp.emc_dvfs_table; struct tegra_dc_ext_imp_emc_dvfs_pair ext_pairs[dvfs_table->num_pairs]; u32 num_pairs_to_cpy = 0; size_t i; int emc_to_dram_factor = bwmgr_get_emc_to_dram_freq_factor(); int ret = 0; num_pairs_to_cpy = min(dvfs_table->num_pairs, num_pairs_requested); for (i = 0; i < num_pairs_to_cpy; i++) { ext_pairs[i].freq = dvfs_table->pairs[i].freq / emc_to_dram_factor; ext_pairs[i].latency = dvfs_table->pairs[i].latency; } /* * If lock mode is enabled, the EMC frequency may have been locked by an * external client. As such, filter the EMC DVFS table based on the * current max EMC rate. */ if (unlikely(g_imp.lock_mode_enabled)) { unsigned long max_emc_khz = tegra_bwmgr_round_rate(ULONG_MAX) / emc_to_dram_factor / 1000; for (i = num_pairs_to_cpy - 1; i >= 0; i--) { if (ext_pairs[i].freq > max_emc_khz) num_pairs_to_cpy--; else if (ext_pairs[i].freq <= max_emc_khz) break; } } if (copy_to_user(ext_dvfs_ptr, ext_pairs, num_pairs_to_cpy * sizeof(*ext_pairs))) { pr_err("%s: Can't copy DVFS pairs to user\n", __func__); ret = -EFAULT; } else { *num_pairs_returned = num_pairs_to_cpy; } return ret; } int tegra_nvdisp_get_imp_user_info(struct tegra_dc_ext_imp_user_info *info) { int ret = 0; info->mempool_size = g_imp.mc_caps.total_mempool_size_bytes; ret = tegra_nvdisp_get_v_taps_user_info(info); if (ret) return ret; return cpy_dvfs_pairs_to_user(info->emc_dvfs_pairs, info->emc_dvfs_pairs_requested, &info->emc_dvfs_pairs_returned); } EXPORT_SYMBOL(tegra_nvdisp_get_imp_user_info); static int cpy_thread_info_to_user(struct tegra_dc_ext_imp_caps *imp_caps) { int max_wins = tegra_dc_get_numof_dispwindows(); struct tegra_dc_ext_imp_thread_info *ext_info_arr; struct tegra_dc_ext_imp_thread_info *ext_info; struct tegra_dc_ext_imp_thread_info *thread_info_map[max_wins]; u32 num_info = imp_caps->num_thread_info; struct nvdisp_imp_table *imp_table; struct tegra_nvdisp_imp_settings *boot_setting; int i, ret = 0; imp_table = tegra_dc_common_get_imp_table(); if (!imp_table || !imp_table->boot_setting) { pr_err("%s: No IMP boot setting found\n", __func__); return -ENODEV; } boot_setting = imp_table->boot_setting; if (num_info > max_wins) { pr_err("%s: Num thread info (%u) > max wins (%d)\n", __func__, num_info, max_wins); return -E2BIG; } ext_info_arr = kcalloc(num_info, sizeof(*ext_info_arr), GFP_KERNEL); if (!ext_info_arr) return -ENOMEM; if (copy_from_user(ext_info_arr, imp_caps->thread_info, num_info * sizeof(*ext_info_arr))) { pr_err("%s: Can't copy thread info from user\n", __func__); ret = -EFAULT; goto free_thread_info_ret; } for (i = 0; i < max_wins; i++) thread_info_map[i] = NULL; for (i = 0; i < num_info; i++) { ext_info = &ext_info_arr[i]; if (ext_info->win_id >= max_wins) { pr_err("%s: Win id (%u) >= max wins (%d)\n", __func__, ext_info->win_id, max_wins); ret = -EINVAL; goto free_thread_info_ret; } thread_info_map[ext_info->win_id] = ext_info; } for (i = 0; i < boot_setting->num_heads; i++) { struct tegra_nvdisp_imp_head_settings *head_settings; int j; head_settings = &boot_setting->head_settings[i]; for (j = 0; j < head_settings->num_wins; j++) { struct tegra_dc_ext_nvdisp_imp_win_entries *win_entries; win_entries = &head_settings->win_entries[j]; ext_info = thread_info_map[win_entries->id]; if (!ext_info) continue; ext_info->thread_group = win_entries->thread_group; } } if (copy_to_user(imp_caps->thread_info, ext_info_arr, num_info * sizeof(*ext_info_arr))) { pr_err("%s: Failed to copy thread info to user\n", __func__); ret = -EFAULT; } free_thread_info_ret: kfree(ext_info_arr); return ret; } int tegra_nvdisp_get_imp_caps(struct tegra_dc_ext_imp_caps *imp_caps) { int ret = 0; imp_caps->mc_caps = g_imp.mc_caps; /* * If lock mode is enabled, the max hubclk frequency may have been * locked by an external client. Re-query the max hubclk rate * accordingly. */ if (unlikely(g_imp.lock_mode_enabled)) imp_caps->mc_caps.peak_hubclk_hz = clk_round_rate(hubclk, ULONG_MAX); ret = cpy_dvfs_pairs_to_user(imp_caps->dvfs_pairs, imp_caps->num_dvfs_requested, &imp_caps->num_dvfs_returned); if (ret) return ret; return cpy_thread_info_to_user(imp_caps); } EXPORT_SYMBOL(tegra_nvdisp_get_imp_caps); struct tegra_nvdisp_imp_settings *tegra_nvdisp_get_current_imp_settings(void) { return list_first_entry_or_null(&g_imp.imp_settings_queue, struct tegra_nvdisp_imp_settings, imp_node); } u32 tegra_nvdisp_get_max_pending_bw(struct tegra_dc *dc) { struct tegra_nvdisp_imp_settings *settings; u32 max_pending_bw = 0; list_for_each_entry(settings, &g_imp.imp_settings_queue, imp_node) { u32 pending_bw = settings->global_entries.total_iso_bw_with_catchup_kBps; if (pending_bw > max_pending_bw) max_pending_bw = pending_bw; } return max_pending_bw; } static inline bool tegra_nvdisp_common_channel_is_clean(struct tegra_dc *dc) { return !dc->common_channel_pending; } static void tegra_nvdisp_enable_common_channel_intr(struct tegra_dc *dc) { if (!tegra_nvdisp_is_dc_active(dc)) return; /* * WIN_X_ACT_REQs are checked at VBLANK in NC_DISPLAY mode, and at * FRAME_END in C_DISPLAY mode. Be consistent and check COMMON_ACT_REQ * in the same spot. */ if (dc->out->flags & TEGRA_DC_OUT_ONE_SHOT_MODE) { mutex_lock(&dc->lock); set_bit(V_BLANK_IMP, &dc->vblank_ref_count); tegra_dc_unmask_interrupt(dc, V_BLANK_INT); mutex_unlock(&dc->lock); } else { tegra_dc_config_frame_end_intr(dc, true); } dc->common_channel_intr_enabled = true; } void tegra_nvdisp_set_common_channel_pending(struct tegra_dc *dc) { if (tegra_nvdisp_is_dc_active(dc)) dc->common_channel_pending = true; } static void tegra_nvdisp_activate_common_channel(struct tegra_dc *dc) { tegra_nvdisp_enable_common_channel_intr(dc); tegra_dc_writel(dc, nvdisp_cmd_state_ctrl_common_act_update_enable_f(), nvdisp_cmd_state_ctrl_r()); tegra_dc_readl(dc, nvdisp_cmd_state_ctrl_r()); /* flush */ tegra_dc_writel(dc, nvdisp_cmd_state_ctrl_common_act_req_enable_f(), nvdisp_cmd_state_ctrl_r()); tegra_dc_readl(dc, nvdisp_cmd_state_ctrl_r()); /* flush */ mutex_lock(&tegra_nvdisp_lock); tegra_nvdisp_set_common_channel_pending(dc); mutex_unlock(&tegra_nvdisp_lock); } static void tegra_nvdisp_wait_for_common_channel_to_promote(struct tegra_dc *dc) { struct nvdisp_request_wq *promotion_wq; int timeout = 0; promotion_wq = &g_imp.common_channel_promotion_wq; mutex_lock(&tegra_nvdisp_lock); if (tegra_nvdisp_common_channel_is_clean(dc)) { mutex_unlock(&tegra_nvdisp_lock); return; } /* * Use an exclusive wait since only one HEAD can program COMMON channel * state at any given time. */ timeout = promotion_wq->timeout_per_entry; ___wait_event(promotion_wq->wq, ___wait_cond_timeout(tegra_nvdisp_common_channel_is_clean(dc)), TASK_UNINTERRUPTIBLE, WQ_FLAG_EXCLUSIVE, timeout, mutex_unlock(&tegra_nvdisp_lock); __ret = schedule_timeout(__ret); mutex_lock(&tegra_nvdisp_lock)); if (!tegra_nvdisp_common_channel_is_clean(dc)) dev_err(&dc->ndev->dev, "%s: DC %d timed out waiting for COMMON promotion\n", __func__, dc->ctrl_num); mutex_unlock(&tegra_nvdisp_lock); } static void tegra_nvdisp_notify_common_channel_promoted(struct tegra_dc *dc) { /* Wake up one exclusive waiter. There should only be one. */ dc->common_channel_pending = false; dc->common_channel_intr_enabled = false; wake_up_nr(&g_imp.common_channel_promotion_wq.wq, 1); } static bool tegra_nvdisp_common_channel_is_free(void) { int i; for (i = 0; i < tegra_dc_get_numof_dispheads(); i++) { struct tegra_dc *dc = tegra_dc_get_dc(i); if (dc && dc->common_channel_reserved) return false; } return true; } int tegra_dc_reserve_common_channel(struct tegra_dc *dc) { struct nvdisp_request_wq *reservation_wq; int cur_nr_pending = 0; int timeout = 0; int ret = 0; if (!tegra_dc_is_nvdisplay()) return 0; reservation_wq = &g_imp.common_channel_reservation_wq; mutex_lock(&tegra_nvdisp_lock); cur_nr_pending = atomic_read(&reservation_wq->nr_pending); atomic_inc(&reservation_wq->nr_pending); if (tegra_nvdisp_common_channel_is_free()) { dc->common_channel_reserved = true; mutex_unlock(&tegra_nvdisp_lock); return ret; } /* * Scale the per-entry timeout value by the number of outstanding * requests that need to be serviced before this one. */ timeout = reservation_wq->timeout_per_entry; timeout *= max(cur_nr_pending, 1); /* * Use an exclusive wait to queue and wake up processes in a FIFO order. * This ensures that the list of pending waiters is always aligned with * the global queue of IMP settings. */ ret = ___wait_event(reservation_wq->wq, ___wait_cond_timeout(tegra_nvdisp_common_channel_is_free()), TASK_UNINTERRUPTIBLE, WQ_FLAG_EXCLUSIVE, timeout, mutex_unlock(&tegra_nvdisp_lock); __ret = schedule_timeout(__ret); mutex_lock(&tegra_nvdisp_lock)); /* * If we timed out, manually restore "nr_pending" and return an error. */ if (!tegra_nvdisp_common_channel_is_free()) { atomic_dec(&reservation_wq->nr_pending); mutex_unlock(&tegra_nvdisp_lock); dev_err(&dc->ndev->dev, "%s: DC %d timed out waiting for the COMMON channel\n", __func__, dc->ctrl_num); ret = -EINVAL; return ret; } ret = 0; dc->common_channel_reserved = true; mutex_unlock(&tegra_nvdisp_lock); return ret; } EXPORT_SYMBOL(tegra_dc_reserve_common_channel); void tegra_dc_release_common_channel(struct tegra_dc *dc) { struct nvdisp_request_wq *reservation_wq; if (!tegra_dc_is_nvdisplay()) return; reservation_wq = &g_imp.common_channel_reservation_wq; mutex_lock(&tegra_nvdisp_lock); if (!dc->common_channel_reserved) { mutex_unlock(&tegra_nvdisp_lock); return; } /* Wake up the next exclusive waiter. */ dc->common_channel_reserved = false; atomic_dec(&reservation_wq->nr_pending); wake_up_nr(&reservation_wq->wq, 1); mutex_unlock(&tegra_nvdisp_lock); } EXPORT_SYMBOL(tegra_dc_release_common_channel); bool tegra_dc_handle_common_channel_promotion(struct tegra_dc *dc) { /* * COMMON channel state is promoted on the very next loadv boundary for * whichever HEAD set COMMON_ACT_REQ. Notify whichever HEAD is waiting * if this condition has been met. */ u32 val; u32 dirty; bool clear_intr = false; if (!tegra_dc_is_nvdisplay()) return false; val = tegra_dc_readl(dc, DC_CMD_STATE_CONTROL); dirty = !!(val & COMMON_ACT_REQ); mutex_lock(&tegra_nvdisp_lock); if (dc->common_channel_pending && !dirty) { clear_intr = dc->common_channel_intr_enabled; tegra_nvdisp_notify_common_channel_promoted(dc); } mutex_unlock(&tegra_nvdisp_lock); return clear_intr; } EXPORT_SYMBOL(tegra_dc_handle_common_channel_promotion); static void dealloc_imp_settings( struct tegra_nvdisp_imp_settings *imp_settings) { struct tegra_nvdisp_mempool_req *req; int i; if (!imp_settings) return; for (i = 0; i < imp_settings->num_heads; i++) { struct tegra_nvdisp_imp_head_settings *head_settings; head_settings = &imp_settings->head_settings[i]; if (head_settings->num_wins > 0) kfree(head_settings->win_entries); } if (imp_settings->decreasing_pool_reqs) { for (i = 0; i < imp_settings->num_heads; i++) { req = &imp_settings->decreasing_pool_reqs[i]; kfree(req->win_ids); kfree(req->win_entries); } } if (imp_settings->increasing_pool_reqs) { for (i = 0; i < imp_settings->num_heads; i++) { req = &imp_settings->increasing_pool_reqs[i]; kfree(req->win_ids); kfree(req->win_entries); } } kfree(imp_settings->decreasing_pool_reqs); kfree(imp_settings->increasing_pool_reqs); if (imp_settings->num_heads > 0) kfree(imp_settings->head_settings); kfree(imp_settings); } static int alloc_pool_reqs(struct tegra_nvdisp_imp_settings *imp_settings) { size_t pool_reqs_size; int i; pool_reqs_size = sizeof(*(imp_settings->decreasing_pool_reqs)) * imp_settings->num_heads; imp_settings->decreasing_pool_reqs = kzalloc(pool_reqs_size, GFP_KERNEL); if (!imp_settings->decreasing_pool_reqs) { pr_err("%s: Failed to alloc mem for dec mempool\n", __func__); return -ENOMEM; } imp_settings->increasing_pool_reqs = kzalloc(pool_reqs_size, GFP_KERNEL); if (!imp_settings->increasing_pool_reqs) { pr_err("%s: Failed to alloc mem for inc mempool\n", __func__); return -ENOMEM; } for (i = 0; i < imp_settings->num_heads; i++) { struct tegra_nvdisp_mempool_req *dec_pool, *inc_pool; size_t win_ids_size, win_entries_size; dec_pool = &imp_settings->decreasing_pool_reqs[i]; inc_pool = &imp_settings->increasing_pool_reqs[i]; win_ids_size = sizeof(*(dec_pool->win_ids)) * tegra_dc_get_numof_dispwindows(); win_entries_size = sizeof(*(dec_pool->win_entries)) * tegra_dc_get_numof_dispwindows(); dec_pool->win_ids = kzalloc(win_ids_size, GFP_KERNEL); dec_pool->win_entries = kzalloc(win_entries_size, GFP_KERNEL); inc_pool->win_ids = kzalloc(win_ids_size, GFP_KERNEL); inc_pool->win_entries = kzalloc(win_entries_size, GFP_KERNEL); if (!dec_pool->win_ids || !dec_pool->win_entries || !inc_pool->win_ids || !inc_pool->win_entries) { pr_err("%s: No mem for mempool win\n", __func__); return -ENOMEM; } } return 0; } static struct tegra_nvdisp_imp_settings *alloc_imp_settings( u8 num_heads, u8 *num_wins_per_head) { struct tegra_nvdisp_imp_settings *imp_settings; int i; imp_settings = kzalloc(sizeof(*imp_settings), GFP_KERNEL); if (!imp_settings) { pr_err("%s: Failed to alloc mem for settings\n", __func__); return imp_settings; } imp_settings->head_settings = kcalloc(num_heads, sizeof(*(imp_settings->head_settings)), GFP_KERNEL); if (!imp_settings->head_settings) { pr_err("%s: Failed to alloc mem for heads\n", __func__); kfree(imp_settings); return NULL; } imp_settings->num_heads = num_heads; for (i = 0; i < num_heads; i++) { struct tegra_nvdisp_imp_head_settings *head_settings; head_settings = &imp_settings->head_settings[i]; head_settings->win_entries = kcalloc(num_wins_per_head[i], sizeof(*(head_settings->win_entries)), GFP_KERNEL); if (!head_settings->win_entries) { pr_err("%s: Failed to alloc mem for wins\n", __func__); dealloc_imp_settings(imp_settings); return NULL; } head_settings->num_wins = num_wins_per_head[i]; } if (alloc_pool_reqs(imp_settings)) { dealloc_imp_settings(imp_settings); imp_settings = NULL; } return imp_settings; } static void cpy_from_ext_imp_head_v1( struct tegra_dc_ext_imp_head_results *head_results, struct tegra_nvdisp_imp_head_settings *nvdisp_head, u8 ctrl_num) { struct tegra_dc_ext_nvdisp_imp_head_entries *head_entries; u8 num_wins = head_results->num_windows; int i; head_entries = &nvdisp_head->entries; head_entries->ctrl_num = ctrl_num; head_entries->curs_fetch_slots = head_results->metering_slots_value_cursor; head_entries->curs_pipe_meter = head_results->pipe_meter_value_cursor; head_entries->curs_dvfs_watermark = head_results->thresh_lwm_dvfs_cursor; head_entries->curs_mempool_entries = head_results->pool_config_entries_cursor; for (i = 0; i < num_wins; i++) { struct tegra_dc_ext_nvdisp_imp_win_entries *win_entries; win_entries = &nvdisp_head->win_entries[i]; win_entries->id = head_results->win_ids[i]; win_entries->thread_group = head_results->thread_group_win[i]; win_entries->fetch_slots = head_results->metering_slots_value_win[i]; win_entries->pipe_meter = head_results->pipe_meter_value_win[i]; win_entries->dvfs_watermark = head_results->thresh_lwm_dvfs_win[i]; win_entries->mempool_entries = head_results->pool_config_entries_win[i]; } } static struct tegra_nvdisp_imp_settings *cpy_from_ext_imp_settings_v1( struct tegra_dc_ext_imp_settings *ext_settings) { struct tegra_nvdisp_imp_settings *nvdisp_settings; struct tegra_dc_ext_nvdisp_imp_global_entries *global_entries; u8 active_heads = 0, max_heads = tegra_dc_get_numof_dispheads(); u8 num_wins_per_head[max_heads]; int i; for (i = 0; i < max_heads; i++) { struct tegra_dc_ext_imp_head_results *head_results; head_results = &ext_settings->imp_results[i]; num_wins_per_head[i] = 0; if (head_results->head_active) num_wins_per_head[active_heads++] = head_results->num_windows; } nvdisp_settings = alloc_imp_settings(active_heads, num_wins_per_head); if (!nvdisp_settings) return NULL; global_entries = &nvdisp_settings->global_entries; global_entries->total_win_fetch_slots = ext_settings->window_slots_value; global_entries->total_curs_fetch_slots = ext_settings->cursor_slots_value; global_entries->emc_floor_hz = ext_settings->proposed_emc_hz; global_entries->min_hubclk_hz = ext_settings->hubclk; global_entries->total_iso_bw_with_catchup_kBps = ext_settings->total_display_iso_bw_kbps; global_entries->total_iso_bw_without_catchup_kBps = ext_settings->required_total_bw_kbps; active_heads = 0; for (i = 0; i < max_heads; i++) { struct tegra_dc_ext_imp_head_results *head_results; struct tegra_nvdisp_imp_head_settings *nvdisp_head; head_results = &ext_settings->imp_results[i]; if (!head_results->head_active) continue; nvdisp_head = &nvdisp_settings->head_settings[active_heads++]; cpy_from_ext_imp_head_v1(head_results, nvdisp_head, i); } return nvdisp_settings; } static int cpy_from_ext_imp_head_v2( struct tegra_dc_ext_nvdisp_imp_head_settings *ext_head, struct tegra_nvdisp_imp_head_settings *nvdisp_head) { struct tegra_dc_ext_nvdisp_imp_win_settings *ext_wins; u8 num_wins = ext_head->num_wins; int i, ret = 0; ext_wins = kcalloc(num_wins, sizeof(*ext_wins), GFP_KERNEL); if (!ext_wins) { pr_err("%s: Failed to alloc mem for dc_ext wins\n", __func__); return -ENOMEM; } if (copy_from_user(ext_wins, (void __user *)ext_head->win_settings, sizeof(*ext_wins) * num_wins)) { pr_err("%s: Failed to copy dc_ext wins from user\n", __func__); ret = -EFAULT; goto cpy_imp_wins_ret; } nvdisp_head->entries = ext_head->entries; for (i = 0; i < num_wins; i++) nvdisp_head->win_entries[i] = ext_wins[i].entries; cpy_imp_wins_ret: kfree(ext_wins); return ret; } static struct tegra_nvdisp_imp_settings *cpy_from_ext_imp_settings_v2( struct tegra_dc_ext_nvdisp_imp_settings *ext_settings) { struct tegra_nvdisp_imp_settings *nvdisp_settings = NULL; struct tegra_dc_ext_nvdisp_imp_head_settings *ext_heads; u8 num_heads = ext_settings->num_heads; u8 max_heads = tegra_dc_get_numof_dispheads(); u8 num_wins_per_head[max_heads]; int i, ret = 0; ext_heads = kcalloc(num_heads, sizeof(*ext_heads), GFP_KERNEL); if (!ext_heads) { pr_err("%s: Failed to alloc mem for dc_ext heads\n", __func__); return NULL; } if (copy_from_user(ext_heads, (void __user *)ext_settings->head_settings, sizeof(*ext_heads) * num_heads)) { pr_err("%s: Failed to copy dc_ext heads from user\n", __func__); goto cpy_imp_v2_ret; } for (i = 0; i < num_heads; i++) num_wins_per_head[i] = ext_heads[i].num_wins; /* * Zero out the remaining entries individually. We can't use memset() * since this is a variable-length array. */ for (; i < max_heads; i++) num_wins_per_head[i] = 0; nvdisp_settings = alloc_imp_settings(num_heads, num_wins_per_head); if (!nvdisp_settings) goto cpy_imp_v2_ret; nvdisp_settings->global_entries = ext_settings->global_settings.entries; for (i = 0; i < num_heads; i++) { if (ext_heads[i].num_wins < 1 || ext_heads[i].num_wins > DC_N_WINDOWS) { pr_err("Wrong number of displays"); goto cpy_imp_v2_ret; } ret = cpy_from_ext_imp_head_v2(&ext_heads[i], &nvdisp_settings->head_settings[i]); if (ret) { dealloc_imp_settings(nvdisp_settings); nvdisp_settings = NULL; goto cpy_imp_v2_ret; } } cpy_imp_v2_ret: kfree(ext_heads); return nvdisp_settings; } static struct tegra_nvdisp_imp_settings *convert_dc_ext_to_nvdisp_imp( struct tegra_dc_ext_flip_user_data *flip_user_data, void __user *ext_settings_ptr, struct tegra_dc_ext_imp_settings *ext_settings_v1, struct tegra_dc_ext_nvdisp_imp_settings *ext_settings_v2) { struct tegra_nvdisp_imp_settings *nvdisp_settings; bool use_v2 = flip_user_data->flags & TEGRA_DC_EXT_FLIP_FLAG_IMP_V2; int ret = 0; if (use_v2) ret = copy_from_user(ext_settings_v2, ext_settings_ptr, sizeof(*ext_settings_v2)); else ret = copy_from_user(ext_settings_v1, ext_settings_ptr, sizeof(*ext_settings_v1)); if (ret) { pr_err("%s: Failed to copy dc_ext settings\n", __func__); return NULL; } if (use_v2) nvdisp_settings = cpy_from_ext_imp_settings_v2(ext_settings_v2); else nvdisp_settings = cpy_from_ext_imp_settings_v1(ext_settings_v1); if (!nvdisp_settings) pr_err("%s: Failed to create nvdisp IMP settings\n", __func__); return nvdisp_settings; } int tegra_dc_queue_imp_propose(struct tegra_dc *dc, struct tegra_dc_ext_flip_user_data *flip_user_data) { struct tegra_dc_ext_imp_settings ext_settings_v1; struct tegra_dc_ext_nvdisp_imp_settings ext_settings_v2; struct tegra_nvdisp_imp_settings *nvdisp_settings; struct tegra_dc_ext_nvdisp_imp_global_entries *global_entries; void __user *ext_session_id_ptr; void __user *ext_settings_ptr; bool use_v2 = flip_user_data->flags & TEGRA_DC_EXT_FLIP_FLAG_IMP_V2; int ret = 0; if (!tegra_dc_is_nvdisplay()) return -EINVAL; ext_settings_ptr = (void __user *)flip_user_data->imp_ptr.settings; nvdisp_settings = convert_dc_ext_to_nvdisp_imp(flip_user_data, ext_settings_ptr, &ext_settings_v1, &ext_settings_v2); if (!nvdisp_settings) return -EINVAL; mutex_lock(&tegra_nvdisp_lock); global_entries = &nvdisp_settings->global_entries; ret = tegra_nvdisp_negotiate_reserved_bw(dc, (u32)global_entries->total_iso_bw_with_catchup_kBps, (u32)global_entries->total_iso_bw_without_catchup_kBps, (u32)global_entries->emc_floor_hz, (u32)global_entries->min_hubclk_hz); if (ret) { mutex_unlock(&tegra_nvdisp_lock); dealloc_imp_settings(nvdisp_settings); return ret; } if (use_v2) { u32 offset = offsetof(struct tegra_dc_ext_nvdisp_imp_settings, session_id); ext_session_id_ptr = ext_settings_ptr + offset; } else { ext_session_id_ptr = (void __user *)ext_settings_v1.session_id_ptr; } if (copy_to_user(ext_session_id_ptr, &dc->imp_session_id_cntr, sizeof(dc->imp_session_id_cntr))) { dev_err(&dc->ndev->dev, "Failed to copy IMP session id back to user\n"); mutex_unlock(&tegra_nvdisp_lock); if (nvdisp_settings->num_heads > 0) dealloc_imp_settings(nvdisp_settings); else dev_err(&dc->ndev->dev, "num heads is not a positive integer\n"); return -EFAULT; } nvdisp_settings->session_id = dc->imp_session_id_cntr++; nvdisp_settings->owner_ctrl_num = dc->ctrl_num; INIT_LIST_HEAD(&nvdisp_settings->imp_node); list_add_tail(&nvdisp_settings->imp_node, &g_imp.imp_settings_queue); mutex_unlock(&tegra_nvdisp_lock); trace_display_imp_propose_queued(dc->ctrl_num, nvdisp_settings->session_id); return ret; } EXPORT_SYMBOL(tegra_dc_queue_imp_propose); static void tegra_nvdisp_flush_imp_queue(void) { struct tegra_nvdisp_imp_settings *cur = NULL, *next = NULL; mutex_lock(&tegra_nvdisp_lock); list_for_each_entry_safe(cur, next, &g_imp.imp_settings_queue, imp_node) { list_del(&cur->imp_node); dealloc_imp_settings(cur); } mutex_unlock(&tegra_nvdisp_lock); } int tegra_dc_validate_imp_queue(struct tegra_dc *dc, u64 session_id) { struct tegra_nvdisp_imp_settings *cur = NULL, *next = NULL; int ret = -EINVAL; if (!tegra_dc_is_nvdisplay()) return ret; mutex_lock(&tegra_nvdisp_lock); /* * Since IMP flips are issued in the same respective order as their * corresponding PROPOSEs, and only one IMP flip can be in-flight at any * moment in time, any entries that are in front of the one we're * looking for must be stale. These entries can be safely removed. * * If none of the entries in the queue match the one we're looking for, * return an error. */ list_for_each_entry_safe(cur, next, &g_imp.imp_settings_queue, imp_node) { if (cur->owner_ctrl_num != dc->ctrl_num || cur->session_id != session_id) { list_del(&cur->imp_node); dealloc_imp_settings(cur); } else { ret = 0; break; } } mutex_unlock(&tegra_nvdisp_lock); return ret; } EXPORT_SYMBOL(tegra_dc_validate_imp_queue); struct tegra_dc *find_dc_by_ctrl_num(u32 ctrl_num) { int i; for (i = 0; i < tegra_dc_get_numof_dispheads(); i++) { struct tegra_dc *dc = tegra_dc_get_dc(i); if (dc && dc->ctrl_num == ctrl_num) return dc; } return NULL; } static void tegra_nvdisp_generate_mempool_reqs(struct tegra_dc *dc, struct tegra_nvdisp_imp_settings *imp_settings) { int i; for (i = 0; i < imp_settings->num_heads; i++) { struct tegra_nvdisp_imp_head_settings *head_settings; struct tegra_nvdisp_mempool_req *dec_pool, *inc_pool; struct tegra_nvdisp_mempool_req *cur_pool = NULL; struct tegra_dc *other_dc; u32 old_val, new_val; u8 num_dec, num_inc; int j; head_settings = &imp_settings->head_settings[i]; other_dc = find_dc_by_ctrl_num(head_settings->entries.ctrl_num); if (!other_dc || !other_dc->enabled || other_dc == dc) continue; num_dec = imp_settings->num_decreasing_pool; num_inc = imp_settings->num_increasing_pool; dec_pool = &imp_settings->decreasing_pool_reqs[num_dec]; inc_pool = &imp_settings->increasing_pool_reqs[num_inc]; old_val = tegra_nvdisp_get_active_curs_pool(other_dc); new_val = head_settings->entries.curs_mempool_entries; if (new_val < old_val) cur_pool = dec_pool; else if (new_val > old_val) cur_pool = inc_pool; if (cur_pool) { cur_pool->program_cursor = true; cur_pool->cursor_entry = new_val; cur_pool = NULL; } for (j = 0; j < head_settings->num_wins; j++) { struct tegra_dc_ext_nvdisp_imp_win_entries *win_entries; struct tegra_dc_win *win; win_entries = &head_settings->win_entries[j]; win = tegra_dc_get_window(other_dc, win_entries->id); if (!win) continue; old_val = tegra_nvdisp_get_active_win_pool(win); new_val = win_entries->mempool_entries; if (new_val < old_val) cur_pool = dec_pool; else if (new_val > old_val) cur_pool = inc_pool; if (cur_pool) { u8 num_wins = cur_pool->num_wins; cur_pool->win_ids[num_wins] = win_entries->id; cur_pool->win_entries[num_wins] = new_val; cur_pool->num_wins++; cur_pool = NULL; } } if (dec_pool->program_cursor || dec_pool->num_wins > 0) { dec_pool->ctrl_num = other_dc->ctrl_num; imp_settings->num_decreasing_pool++; } if (inc_pool->program_cursor || inc_pool->num_wins > 0) { inc_pool->ctrl_num = other_dc->ctrl_num; imp_settings->num_increasing_pool++; } } } static void tegra_nvdisp_program_dc_mempool(struct tegra_dc *dc, struct tegra_nvdisp_mempool_req *mempool_req) { u32 entries = 0; int i; if (!dc || !dc->enabled) return; if (mempool_req->program_cursor) { entries = mempool_req->cursor_entry; tegra_dc_writel(dc, nvdisp_ihub_cursor_pool_config_entries_f(entries), nvdisp_ihub_cursor_pool_config_r()); trace_display_imp_cursor_mempool_programmed(dc->ctrl_num, entries); } for (i = 0; i < mempool_req->num_wins; i++) { struct tegra_dc_win *win; win = tegra_dc_get_window(dc, mempool_req->win_ids[i]); if (!win) continue; entries = mempool_req->win_entries[i]; nvdisp_win_write(win, win_ihub_pool_config_entries_f(entries), win_ihub_pool_config_r()); trace_display_imp_win_mempool_programmed(dc->ctrl_num, win->idx, entries); } tegra_nvdisp_activate_common_channel(dc); tegra_nvdisp_wait_for_common_channel_to_promote(dc); } static void tegra_nvdisp_program_other_mempool(struct tegra_dc *dc, struct tegra_nvdisp_imp_settings *imp_settings, bool before_win_update) { struct tegra_nvdisp_mempool_req *mempool_reqs; int i, num_reqs; if (before_win_update) { mempool_reqs = imp_settings->decreasing_pool_reqs; num_reqs = imp_settings->num_decreasing_pool; } else { mempool_reqs = imp_settings->increasing_pool_reqs; num_reqs = imp_settings->num_increasing_pool; } for (i = 0; i < num_reqs; i++) { struct tegra_nvdisp_mempool_req *req; struct tegra_dc *other_dc; req = &mempool_reqs[i]; other_dc = find_dc_by_ctrl_num(req->ctrl_num); if (!other_dc) continue; tegra_nvdisp_program_dc_mempool(other_dc, req); } } void tegra_dc_adjust_imp(struct tegra_dc *dc, bool before_win_update) { struct tegra_nvdisp_imp_settings *imp_settings; struct tegra_dc_ext_nvdisp_imp_global_entries *global_entries; if (!tegra_dc_is_nvdisplay()) return; if (!dc) { pr_err("%s: DC is NULL\n", __func__); return; } if (!dc->enabled) { pr_err("%s: DC %d is NOT enabled\n", __func__, dc->ctrl_num); return; } mutex_lock(&tegra_nvdisp_lock); imp_settings = tegra_nvdisp_get_current_imp_settings(); if (!imp_settings) { mutex_unlock(&tegra_nvdisp_lock); return; } /* * - The cursor and wgrp latency registers take effect immediately. * As such, disable latency events for now and re-enable them after * the rest of the window channel state has promoted. * - Make sure our ISO bandwidth and hubclk requirements are met before * changing the current isohub settings. */ global_entries = &imp_settings->global_entries; if (before_win_update) { tegra_nvdisp_disable_ihub_latency_events(dc); tegra_nvdisp_program_bandwidth(dc, (u32)global_entries->total_iso_bw_with_catchup_kBps, (u32)global_entries->total_iso_bw_without_catchup_kBps, (u32)global_entries->emc_floor_hz, (u32)global_entries->min_hubclk_hz, before_win_update); } mutex_unlock(&tegra_nvdisp_lock); /* Make sure there's no pending change on the current head. */ tegra_nvdisp_wait_for_common_channel_to_promote(dc); if (before_win_update) { mutex_lock(&tegra_nvdisp_lock); tegra_nvdisp_generate_mempool_reqs(dc, imp_settings); mutex_unlock(&tegra_nvdisp_lock); } tegra_nvdisp_program_other_mempool(dc, imp_settings, before_win_update); if (!before_win_update) { mutex_lock(&tegra_nvdisp_lock); /* * Update our bandwidth requirements after the new window state * and isohub settings have promoted. */ tegra_nvdisp_program_bandwidth(dc, (u32)global_entries->total_iso_bw_with_catchup_kBps, (u32)global_entries->total_iso_bw_without_catchup_kBps, (u32)global_entries->emc_floor_hz, (u32)global_entries->min_hubclk_hz, before_win_update); /* Re-enable ihub latency events. */ tegra_nvdisp_enable_ihub_latency_events(dc); /* * These IMP settings are no longer pending. Remove them from * the global queue and free the associated memory. */ list_del(&imp_settings->imp_node); dealloc_imp_settings(imp_settings); mutex_unlock(&tegra_nvdisp_lock); } } EXPORT_SYMBOL(tegra_dc_adjust_imp); static void tegra_nvdisp_program_imp_curs_entries(struct tegra_dc *dc, u32 fetch_slots, u32 pipe_meter, u32 dvfs_watermark, u32 mempool_entries, int owner_head) { tegra_dc_writel(dc, nvdisp_ihub_cursor_fetch_meter_slots_f(fetch_slots), nvdisp_ihub_cursor_fetch_meter_r()); tegra_nvdisp_program_curs_dvfs_watermark(dc, dvfs_watermark); tegra_dc_writel(dc, nvdisp_cursor_pipe_meter_val_f(pipe_meter), nvdisp_cursor_pipe_meter_r()); /* * Only program cursor mempool for the HEAD that's initiating the IMP * state change. The cursor mempool for the other HEADs is taken care of * elsewhere. */ if (dc->ctrl_num == owner_head) { tegra_dc_writel(dc, nvdisp_ihub_cursor_pool_config_entries_f(mempool_entries), nvdisp_ihub_cursor_pool_config_r()); } trace_display_imp_program_cursor(dc->ctrl_num, fetch_slots, pipe_meter, mempool_entries, dvfs_watermark); } static void tegra_nvdisp_program_imp_win_entries(struct tegra_dc *dc, u32 thread_group, u32 fetch_meter, u32 pipe_meter, u32 dvfs_watermark, u32 mempool_entries, u32 win_id, int owner_head) { struct tegra_dc_win *win = tegra_dc_get_window(dc, win_id); if (!win) return; nvdisp_win_write(win, win_ihub_fetch_meter_slots_f(fetch_meter), win_ihub_fetch_meter_r()); tegra_nvdisp_program_win_dvfs_watermark(win, dvfs_watermark); nvdisp_win_write(win, win_precomp_pipe_meter_val_f(pipe_meter), win_precomp_pipe_meter_r()); /* * Only program wgrp mempool for the HEAD that's initiating the IMP * state change. The wgrp mempool for the other HEADs is taken care of * elsewhere. */ if (dc->ctrl_num == owner_head) { nvdisp_win_write(win, win_ihub_pool_config_entries_f(mempool_entries), win_ihub_pool_config_r()); } trace_display_imp_program_win(dc->ctrl_num, win_id, fetch_meter, pipe_meter, mempool_entries, dvfs_watermark, thread_group); } static void tegra_nvdisp_program_imp_head_settings(struct tegra_dc *dc, struct tegra_nvdisp_imp_head_settings *head_settings, u32 owner_head) { struct tegra_dc_ext_nvdisp_imp_head_entries *head_entries; int i; if (!dc || !dc->enabled) return; head_entries = &head_settings->entries; tegra_nvdisp_program_imp_curs_entries(dc, head_entries->curs_fetch_slots, head_entries->curs_pipe_meter, head_entries->curs_dvfs_watermark, head_entries->curs_mempool_entries, owner_head); for (i = 0; i < head_settings->num_wins; i++) { struct tegra_dc_ext_nvdisp_imp_win_entries *win_entries; win_entries = &head_settings->win_entries[i]; tegra_nvdisp_program_imp_win_entries(dc, win_entries->thread_group, win_entries->fetch_slots, win_entries->pipe_meter, win_entries->dvfs_watermark, win_entries->mempool_entries, win_entries->id, owner_head); } } void tegra_nvdisp_program_imp_settings(struct tegra_dc *dc) { struct tegra_nvdisp_imp_settings *imp_settings; int i; if (!dc || !dc->enabled) return; imp_settings = tegra_nvdisp_get_current_imp_settings(); if (!imp_settings) return; for (i = 0; i < imp_settings->num_heads; i++) { struct tegra_nvdisp_imp_head_settings *head_settings; struct tegra_dc *other_dc; head_settings = &imp_settings->head_settings[i]; other_dc = find_dc_by_ctrl_num(head_settings->entries.ctrl_num); if (!other_dc || !other_dc->enabled) continue; tegra_nvdisp_program_imp_head_settings(other_dc, head_settings, dc->ctrl_num); } tegra_nvdisp_program_common_fetch_meter(dc, imp_settings->global_entries.total_curs_fetch_slots, imp_settings->global_entries.total_win_fetch_slots); } static struct tegra_nvdisp_imp_settings *cpy_imp_entries( struct tegra_nvdisp_imp_settings *src_settings) { struct tegra_nvdisp_imp_settings *dst_settings; u32 max_heads = tegra_dc_get_numof_dispheads(); u8 num_wins_per_head[max_heads]; u8 num_heads = src_settings->num_heads; int i; for (i = 0; i < num_heads; i++) { struct tegra_nvdisp_imp_head_settings *head_settings; head_settings = &src_settings->head_settings[i]; num_wins_per_head[i] = head_settings->num_wins; } /* * Zero out the remaining entries individually. We can't use memset() * since this is a variable-length array. */ for (; i < max_heads; i++) num_wins_per_head[i] = 0; dst_settings = alloc_imp_settings(num_heads, num_wins_per_head); if (!dst_settings) return NULL; memcpy(&dst_settings->global_entries, &src_settings->global_entries, sizeof(dst_settings->global_entries)); for (i = 0; i < num_heads; i++) { struct tegra_nvdisp_imp_head_settings *src_head, *dst_head; src_head = &src_settings->head_settings[i]; dst_head = &dst_settings->head_settings[i]; memcpy(&dst_head->entries, &src_head->entries, sizeof(dst_head->entries)); memcpy(dst_head->win_entries, src_head->win_entries, sizeof(*(dst_head->win_entries)) * dst_head->num_wins); } return dst_settings; } static void program_imp_win_owners(bool attach) { int num_wins = tegra_dc_get_numof_dispwindows(); int i; for (i = 0; i < tegra_dc_get_numof_dispheads(); i++) { struct tegra_dc *dc = tegra_dc_get_dc(i); unsigned long winmask; int win_id = 0; if (!dc || !dc->enabled) continue; winmask = dc->pdata->win_mask; for_each_set_bit(win_id, &winmask, num_wins) { struct tegra_dc_win *win; win = tegra_dc_get_window(dc, win_id); if (!win || !win->dc) continue; if (!WIN_IS_ENABLED(win)) { u32 val = win_set_control_owner_none_f(); if (attach) val = dc->ctrl_num; nvdisp_win_write(win, val, win_set_control_r()); } } } } void tegra_dc_reset_imp_state(void) { struct nvdisp_imp_table *imp_table; struct tegra_nvdisp_imp_settings *reset_settings; struct tegra_dc_ext_nvdisp_imp_global_entries *global_entries; struct tegra_dc *reserve_dc = NULL, *master_dc = NULL; struct nvdisp_bandwidth_config max_bw_cfg = {0}; int num_heads = tegra_dc_get_numof_dispheads(); int i; if (!tegra_platform_is_silicon()) return; if (!tegra_dc_is_nvdisplay()) return; /* * Try to find any registered DC instance. It doesn't matter which one * since we just need a non-NULL instance to reserve the COMMON channel. * Although this should never happen, immediately return if there are * none found. */ for (i = 0; i < num_heads; i++) { reserve_dc = tegra_dc_get_dc(i); if (reserve_dc) break; } if (!reserve_dc || tegra_dc_reserve_common_channel(reserve_dc)) return; /* * This function should only be called when all DC handles have been * closed. No userspace clients should be active at this point, and all * pending IMP requests should have already been processed or dropped. * However, we should explicitly flush the queue just to be sure. */ tegra_nvdisp_flush_imp_queue(); /* * No heads should be undergoing any state changes at this point, so * pick any head that's still enabled and pretend that it's triggering * an IMP state change. If there are no active heads, release the COMMON * channel and return. */ for (i = 0; i < num_heads; i++) { master_dc = tegra_dc_get_dc(i); if (master_dc && master_dc->enabled) break; } if (!master_dc || !master_dc->enabled) goto reset_imp_release_common_channel; imp_table = tegra_dc_common_get_imp_table(); if (!imp_table) goto reset_imp_release_common_channel; /* Copy the relevant settings over. */ reset_settings = cpy_imp_entries(imp_table->boot_setting); if (!reset_settings) goto reset_imp_release_common_channel; tegra_nvdisp_get_max_bw_cfg(&max_bw_cfg); global_entries = &reset_settings->global_entries; global_entries->total_iso_bw_with_catchup_kBps = max_bw_cfg.iso_bw; global_entries->total_iso_bw_without_catchup_kBps = max_bw_cfg.total_bw; global_entries->emc_floor_hz = max_bw_cfg.emc_la_floor; global_entries->min_hubclk_hz = max_bw_cfg.hubclk; /* Add the default settings to the IMP queue. */ INIT_LIST_HEAD(&reset_settings->imp_node); list_add_tail(&reset_settings->imp_node, &g_imp.imp_settings_queue); /* Follow the same basic flow as an actual IMP flip. */ if (tegra_nvdisp_negotiate_reserved_bw(master_dc, max_bw_cfg.iso_bw, max_bw_cfg.total_bw, max_bw_cfg.emc_la_floor, max_bw_cfg.hubclk)) { list_del(&reset_settings->imp_node); dealloc_imp_settings(reset_settings); goto reset_imp_release_common_channel; } program_imp_win_owners(true); for (i = 0; i < reset_settings->num_heads; i++) { struct tegra_nvdisp_imp_head_settings *head_settings; struct tegra_dc *dc; head_settings = &reset_settings->head_settings[i]; dc = find_dc_by_ctrl_num(head_settings->entries.ctrl_num); if (!dc) continue; swap_imp_boot_wins(head_settings, dc->pdata->win_mask); } tegra_dc_adjust_imp(master_dc, true); tegra_nvdisp_program_imp_settings(master_dc); if (tegra_dc_enable_update_and_act(master_dc, nvdisp_cmd_state_ctrl_common_act_update_enable_f(), nvdisp_cmd_state_ctrl_common_act_req_enable_f())) dev_err(&master_dc->ndev->dev, "timeout waiting for master dc state to promote\n"); tegra_dc_adjust_imp(master_dc, false); program_imp_win_owners(false); /* * The second adjust IMP call should have already detached the IMP * settings from the global queue and freed them. */ tegra_dc_release_common_channel(reserve_dc); return; reset_imp_release_common_channel: tegra_dc_release_common_channel(reserve_dc); } EXPORT_SYMBOL(tegra_dc_reset_imp_state); void tegra_nvdisp_reg_dump(struct tegra_dc *dc, void *data, void (* print)(void *data, const char *str)) { int i; char buff[256]; const char winname[] = "ABCDEFT"; /* If gated, quietly return. */ if (!tegra_dc_is_powered(dc)) return; mutex_lock(&dc->lock); tegra_dc_get(dc); #define DUMP_REG(a) do { \ snprintf(buff, sizeof(buff), "%-32s\t%03x\t%08lx\n", \ #a, a, tegra_dc_readl(dc, a)); \ print(data, buff); \ } while (0) #include "hw_nvdisp_nvdisp_regdump.c" #undef DUMP_REG #define DUMP_REG(a) do { \ snprintf(buff, sizeof(buff), "%-32s\t%03x\t%08x\n", \ #a, a, nvdisp_win_read(win, a)); \ print(data, buff); \ } while (0) for (i = 0; i < tegra_dc_get_numof_dispwindows(); ++i) { struct tegra_dc_win *win = tegra_dc_get_window(dc, i); if (!win || !win->dc) continue; print(data, "\n"); snprintf(buff, sizeof(buff), "WINDOW %c:\n", winname[i]); print(data, buff); #include "hw_nvdisp_win_regdump.c" #undef DUMP_REG } tegra_dc_put(dc); mutex_unlock(&dc->lock); } void tegra_dc_enable_sor_t18x(struct tegra_dc *dc, int sor_num, bool enable) { u32 enb; u32 reg_val = tegra_dc_readl(dc, nvdisp_win_options_r()); switch (sor_num) { case 0: enb = nvdisp_win_options_sor_set_sor_enable_f(); break; case 1: enb = nvdisp_win_options_sor1_set_sor1_enable_f(); break; default: pr_err("%s: invalid sor_num:%d\n", __func__, sor_num); return; } reg_val = enable ? reg_val | enb : reg_val & ~enb; tegra_dc_writel(dc, reg_val, nvdisp_win_options_r()); } void tegra_nvdisp_set_output_lut(struct tegra_dc *dc, struct tegra_dc_ext_nvdisp_cmu *user_nvdisp_cmu, bool new_cmu_values) { struct tegra_dc_nvdisp_lut *nvdisp_lut; u32 reg_val = 0; dc->cmu_enabled = user_nvdisp_cmu->cmu_enable ? true : false; reg_val = tegra_dc_readl(dc, nvdisp_color_ctl_r()); if (user_nvdisp_cmu->cmu_enable) { reg_val |= nvdisp_color_ctl_cmu_enable_f(); if (new_cmu_values) { nvdisp_lut = &dc->nvdisp_postcomp_lut; nvdisp_copy_output_lut(nvdisp_lut->rgb, (unsigned int *)&user_nvdisp_cmu->rgb, NVDISP_OUTPUT_LUT_SIZE); } } else { reg_val &= ~nvdisp_color_ctl_cmu_enable_f(); } tegra_dc_writel(dc, reg_val, nvdisp_color_ctl_r()); } void tegra_nvdisp_set_output_colorspace(struct tegra_dc *dc, u16 output_colorspace) { u32 csc2_control = dc->cached_settings.csc2_control; /* Reset the csc2 control colorspace */ csc2_control &= ~(nvdisp_csc2_control_output_color_sel_y2020_f()); if (output_colorspace == TEGRA_DC_EXT_FLIP_FLAG_CS_NONE) { csc2_control |= nvdisp_csc2_control_output_color_sel_rgb_f(); } else if (output_colorspace == TEGRA_DC_EXT_FLIP_FLAG_CS_REC601) { csc2_control |= nvdisp_csc2_control_output_color_sel_y601_f(); } else if (output_colorspace == TEGRA_DC_EXT_FLIP_FLAG_CS_REC709) { csc2_control |= nvdisp_csc2_control_output_color_sel_y709_f(); } else if (output_colorspace == TEGRA_DC_EXT_FLIP_FLAG_CS_REC2020) { csc2_control |= nvdisp_csc2_control_output_color_sel_y2020_f(); } else { dev_err(&dc->ndev->dev, "%s: unsupported colorspace=%u\n", __func__, output_colorspace); return; } dc->cached_settings.csc2_control = csc2_control; } void tegra_nvdisp_set_output_range(struct tegra_dc *dc, u8 limited_range_enable) { u32 csc2_control = dc->cached_settings.csc2_control; /* Reset the csc2 control color range */ csc2_control &= ~(nvdisp_csc2_control_limit_rgb_enable_f()); if (limited_range_enable) csc2_control |= nvdisp_csc2_control_limit_rgb_enable_f(); else csc2_control |= nvdisp_csc2_control_limit_rgb_disable_f(); dc->cached_settings.csc2_control = csc2_control; } void tegra_nvdisp_set_csc2(struct tegra_dc *dc) { tegra_dc_writel(dc, dc->cached_settings.csc2_control, nvdisp_csc2_control_r()); } void tegra_nvdisp_activate_general_channel(struct tegra_dc *dc) { /* Send general ack update and request enable */ tegra_dc_writel(dc, nvdisp_cmd_state_ctrl_general_update_enable_f(), nvdisp_cmd_state_ctrl_r()); tegra_dc_readl(dc, nvdisp_cmd_state_ctrl_r()); tegra_dc_writel(dc, nvdisp_cmd_state_ctrl_general_act_req_enable_f(), nvdisp_cmd_state_ctrl_r()); tegra_dc_readl(dc, nvdisp_cmd_state_ctrl_r()); /* flush */ } static struct tegra_dc_sor_info t18x_sor_info[] = { { .hdcp_supported = true }, /* SOR0 */ { .hdcp_supported = true }, /* SOR1 */ }; void tegra_dc_populate_t18x_hw_data(struct tegra_dc_hw_data *hw_data) { if (!hw_data) return; hw_data->nheads = 3; hw_data->nwins = 6; hw_data->nsors = 2; hw_data->sor_info = t18x_sor_info; hw_data->pd_table = &t18x_disp_pd_table; hw_data->valid = true; hw_data->version = TEGRA_DC_HW_T18x; } /* _tegra_nvdisp_enable_vpulse2_int() - initializes and enables vpulse2 * interrupt * @dc : pointer to struct tegra_dc of the current head. * @start_pos : line number at which interrupt is expected. * * Return : 0 if successful else the relevant error number. */ static int _tegra_nvdisp_enable_vpulse2_int(struct tegra_dc *dc, u32 start_pos) { u32 val; if (!dc || !dc->enabled) return -ENODEV; if (start_pos > dc->mode_metadata.vtotal_lines) return -EINVAL; tegra_dc_get(dc); tegra_dc_writel(dc, start_pos, nvdisp_v_pulse2_position_a_r()); val = tegra_dc_readl(dc, nvdisp_disp_signal_option_r()); tegra_dc_writel(dc, val | nvdisp_disp_signal_option_v_pulse2_enable_f(), nvdisp_disp_signal_option_r()); tegra_dc_writel(dc, nvdisp_cmd_state_ctrl_general_act_req_enable_f(), nvdisp_cmd_state_ctrl_r()); val = tegra_dc_readl(dc, nvdisp_cmd_int_enable_r()); tegra_dc_writel(dc, val | V_PULSE2_INT, nvdisp_cmd_int_enable_r()); tegra_dc_put(dc); return 0; } /* _tegra_nvdisp_disable_vpulse2_int() - disables vpulse2 interrupt * @dc : pointer to struct tegra_dc of the current head. * * Return : -ENODEV if dc isn't present or disabled. 0 if * successful. */ static int _tegra_nvdisp_disable_vpulse2_int(struct tegra_dc *dc) { u32 val; if (!dc || !dc->enabled) return -ENODEV; tegra_dc_get(dc); val = tegra_dc_readl(dc, nvdisp_disp_signal_option_r()); tegra_dc_writel(dc, val & ~nvdisp_disp_signal_option_v_pulse2_enable_f() , nvdisp_disp_signal_option_r()); tegra_dc_writel(dc, nvdisp_cmd_state_ctrl_general_act_req_enable_f(), nvdisp_cmd_state_ctrl_r()); val = tegra_dc_readl(dc, nvdisp_cmd_int_enable_r()); tegra_dc_writel(dc, val & ~V_PULSE2_INT, nvdisp_cmd_int_enable_r()); tegra_dc_put(dc); return 0; } /* * tegra_nvdisp_set_msrmnt_mode() - enables latency measurement * mode in dc. * @dc : pointer to struct tegra_dc of the current head. * @enable : tells whether to enable/disable measurement mode. * * Based on the enable value, configures and resets the vpulse2 * interrupt which is used for reading the timestamps. * * Return : void */ void tegra_nvdisp_set_msrmnt_mode(struct tegra_dc *dc, bool enable) { int ret = 0; if (!dc) return; mutex_lock(&dc->msrmnt_info.lock); if (enable && !dc->msrmnt_info.enabled) { dc->msrmnt_info.line_num = dc->mode_metadata.vtotal_lines/2; ret = _tegra_nvdisp_enable_vpulse2_int(dc, dc->msrmnt_info.line_num); if (ret) { mutex_unlock(&dc->msrmnt_info.lock); return; } /* * Wait for the frame_end for v_pulse2 programming above to * take effect. */ _tegra_dc_wait_for_frame_end(dc, div_s64(dc->frametime_ns, 1000000ll) * 2); set_bit(V_PULSE2_LATENCY_MSRMNT, &dc->vpulse2_ref_count); tegra_dc_get(dc); tegra_dc_unmask_interrupt(dc, V_PULSE2_INT); tegra_dc_put(dc); } else if (!enable && dc->msrmnt_info.enabled) { ret = _tegra_nvdisp_disable_vpulse2_int(dc); if (ret) { mutex_unlock(&dc->msrmnt_info.lock); return; } /* * Wait for the frame_end for v_pulse2 programming above to * take effect. */ _tegra_dc_wait_for_frame_end(dc, div_s64(dc->frametime_ns, 1000000ll) * 2); clear_bit(V_PULSE2_LATENCY_MSRMNT, &dc->vpulse2_ref_count); tegra_dc_get(dc); tegra_dc_mask_interrupt(dc, V_PULSE2_INT); tegra_dc_put(dc); } dc->msrmnt_info.enabled = enable; mutex_unlock(&dc->msrmnt_info.lock); } #ifdef CONFIG_DEBUG_FS inline struct dentry *tegra_nvdisp_create_imp_lock_debugfs(struct tegra_dc *dc) { return debugfs_create_bool("imp_lock_mode", 0644, dc->debug_common_dir, &g_imp.lock_mode_enabled); } #endif